Agregue múltiples columnas al dataframe de Pandas desde la función

Tengo un dataframe pandas mydf que tiene dos columnas, y ambas columnas son tipos de datos datetime: mydate y mytime . Quiero agregar tres columnas más: hour , weekday y weeknum .

 def getH(t): #gives the hour return t.hour def getW(d): #gives the week number return d.isocalendar()[1] def getD(d): #gives the weekday return d.weekday() # 0 for Monday, 6 for Sunday mydf["hour"] = mydf.apply(lambda row:getH(row["mytime"]), axis=1) mydf["weekday"] = mydf.apply(lambda row:getD(row["mydate"]), axis=1) mydf["weeknum"] = mydf.apply(lambda row:getW(row["mydate"]), axis=1) 

El fragmento de código funciona, pero no es computacionalmente eficiente ya que recorre el dataframe al menos tres veces. Solo me gustaría saber si hay una manera más rápida y / o más óptima de hacer esto. Por ejemplo, usando zip o merge ? Si, por ejemplo, simplemente creo una función que devuelve tres elementos, ¿cómo debo implementar esto? Para ilustrar, la función sería:

 def getHWd(d,t): return t.hour, d.isocalendar()[1], d.weekday() 

Aquí está el enfoque para hacerlo utilizando una apply

Decir, df es como

 In [64]: df Out[64]: mydate mytime 0 2011-01-01 2011-11-14 1 2011-01-02 2011-11-15 2 2011-01-03 2011-11-16 3 2011-01-04 2011-11-17 4 2011-01-05 2011-11-18 5 2011-01-06 2011-11-19 6 2011-01-07 2011-11-20 7 2011-01-08 2011-11-21 8 2011-01-09 2011-11-22 9 2011-01-10 2011-11-23 10 2011-01-11 2011-11-24 11 2011-01-12 2011-11-25 

Llevaremos la función lambda a una línea separada para facilitar la lectura y la definiremos como

 In [65]: lambdafunc = lambda x: pd.Series([x['mytime'].hour, x['mydate'].isocalendar()[1], x['mydate'].weekday()]) 

Y, apply y almacene el resultado en df[['hour', 'weekday', 'weeknum']]

 In [66]: df[['hour', 'weekday', 'weeknum']] = df.apply(lambdafunc, axis=1) 

Y, la salida es como

 In [67]: df Out[67]: mydate mytime hour weekday weeknum 0 2011-01-01 2011-11-14 0 52 5 1 2011-01-02 2011-11-15 0 52 6 2 2011-01-03 2011-11-16 0 1 0 3 2011-01-04 2011-11-17 0 1 1 4 2011-01-05 2011-11-18 0 1 2 5 2011-01-06 2011-11-19 0 1 3 6 2011-01-07 2011-11-20 0 1 4 7 2011-01-08 2011-11-21 0 1 5 8 2011-01-09 2011-11-22 0 1 6 9 2011-01-10 2011-11-23 0 2 0 10 2011-01-11 2011-11-24 0 2 1 11 2011-01-12 2011-11-25 0 2 2 

Para complementar la respuesta de John Galt :

Dependiendo de la tarea que realice lambdafunc , puede experimentar cierta aceleración al almacenar el resultado de apply en un nuevo DataFrame y luego unirse con el original:

 lambdafunc = lambda x: pd.Series([x['mytime'].hour, x['mydate'].isocalendar()[1], x['mydate'].weekday()]) newcols = df.apply(lambdafunc, axis=1) newcols.columns = ['hour', 'weekday', 'weeknum'] newdf = df.join(newcols) 

Incluso si no ve una mejora en la velocidad, recomendaría usar la join . Podrás evitar la SettingWithCopyWarning (siempre molesta) de SettingWithCopyWarning que puede aparecer al asignar directamente en las columnas:

 SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead 
 def getWd(d): d.isocalendar()[1], d.weekday() def getH(t): return t.hour mydf["hour"] = zip(*df["mytime"].map(getH)) mydf["weekday"], mydf["weeknum"] = zip(*df["mydate"].map(getWd)) 

Puedes hacer esto en un método un tanto más limpio haciendo que la función que apliques devuelva un pd.Series con elementos con nombre:

 def process(row): return pd.Series(dict(b=row["a"] * 2, c=row["a"] + 2)) my_df = pd.DataFrame(dict(a=range(10))) new_df = my_df.join(my_df.apply(process, axis="columns")) 

El resultado es:

  abc 0 0 0 2 1 1 2 3 2 2 4 4 3 3 6 5 4 4 8 6 5 5 10 7 6 6 12 8 7 7 14 9 8 8 16 10 9 9 18 11