Подтвердить что ты не робот

Добавление нескольких столбцов в 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()
4b9b3361

Ответ 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))