Этот вопрос касается эффективного применения пользовательской функции к логическим группам строк в кадре данных Pandas, которые совместно используют значение в некотором столбце.
Рассмотрим следующий пример кадра данных:
sID = [1,1,1,2,4,4,5,5,5]
data = np.random.randn(len(sID))
dates = pd.date_range(start='1/1/2018', periods=len(sID))
mydf = pd.DataFrame({"subject_id":sID, "data":data, "date":dates})
mydf['date'][5] += pd.Timedelta('2 days')
, который выглядит следующим образом:
data date subject_id
0 0.168150 2018-01-01 1
1 -0.484301 2018-01-02 1
2 -0.522980 2018-01-03 1
3 -0.724524 2018-01-04 2
4 0.563453 2018-01-05 4
5 0.439059 2018-01-08 4
6 -1.902182 2018-01-07 5
7 -1.433561 2018-01-08 5
8 0.586191 2018-01-09 5
Представьте себе, что для каждого subject_id
мы хотим вычесть из каждой даты первую дату, встреченную для этого subject_id.Сохранение результата в новом столбце days_elapsed, результат будет выглядеть следующим образом:
data date subject_id days_elapsed
0 0.168150 2018-01-01 1 0
1 -0.484301 2018-01-02 1 1
2 -0.522980 2018-01-03 1 2
3 -0.724524 2018-01-04 2 0
4 0.563453 2018-01-05 4 0
5 0.439059 2018-01-08 4 3
6 -1.902182 2018-01-07 5 0
7 -1.433561 2018-01-08 5 1
8 0.586191 2018-01-09 5 2
Один из естественных способов сделать это - использовать groupby
и apply
:
g_df = mydf.groupby('subject_id')
mydf.loc[:, "days_elapsed"] = g_df["date"].apply(lambda x: x - x.iloc[0]).astype('timedelta64[D]').astype(int)
Однако, если количество групп (идентификаторов субъектов) велико (например, 10 ^ 4) и, скажем, только в 10 раз меньше длины кадра данных, эта очень простая операция действительно медленная.
Есть ли более быстрый метод?
PS: я также попытался установить индекс на subject_id
и затем использовать следующее понимание списка:
def get_first(series, ind):
"Return the first row in a group within a series which (group) potentially can span multiple rows and corresponds to a given index"
group = series.loc[ind]
if hasattr(group, 'iloc'):
return group.iloc[0]
else: # this is for indices with a single element
return group
hind_df = mydf.set_index('subject_id')
A = pd.concat([hind_df["date"].loc[ind] - get_first(hind_df["date"], ind) for ind in np.unique(hind_df.index)])
Однако, это еще медленнее.