Добавление нескольких столбцов в Pandas Dataframe из функции
У меня есть кадр данных pandas mydf
, который имеет два столбца, а оба столбца - это datetime-типы данных: mydate
и mytime
. Я хочу добавить еще три столбца: hour
, weekday
и 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)
Фрагмент работает, но он не является эффективным с точки зрения вычислений, поскольку он циклически проходит через кадр данных как минимум три раза. Я просто хотел бы узнать, есть ли более быстрый и/или более оптимальный способ сделать это. Например, используя zip
или merge
? Если, например, я просто создаю одну функцию, которая возвращает три элемента, как мне это реализовать? Для иллюстрации функция:
def getHWd(d,t):
return t.hour, d.isocalendar()[1], d.weekday()
Ответы
Ответ 1
Чтобы дополнить ответ Джона Галта:
В зависимости от задачи, выполняемой lambdafunc
, вы можете столкнуться с некоторым ускорением, сохранив результат apply
в новом DataFrame
и затем присоединившись к оригиналу:
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)
Даже если вы не видите улучшения скорости, я бы рекомендовал использовать join
. Вы сможете избежать (всегда раздражающего) SettingWithCopyWarning
, который может появиться при назначении непосредственно по столбцам:
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
Ответ 2
Здесь, когда вы делаете это, используйте один apply
Скажем, df
похож на
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
Мы возьмем лямбда-функцию для разделения строки для удобства чтения и определим ее как
In [65]: lambdafunc = lambda x: pd.Series([x['mytime'].hour,
x['mydate'].isocalendar()[1],
x['mydate'].weekday()])
И, apply
и сохраните результат до df[['hour', 'weekday', 'weeknum']]
In [66]: df[['hour', 'weekday', 'weeknum']] = df.apply(lambdafunc, axis=1)
И, результат похож на
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
Ответ 3
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))