从此示例数据开始...
import pandas as pd
start_data = {"person_id": [1, 1, 1, 1, 2], "nid": [1, 2, 3, 4, 1],
"beg": ["Jan 1 2018", "Jan 5 2018", "Jan 10 2018", "Feb 5 2018", "Jan 25 2018"],
"end": ["Feb 1 2018", "Mar 4 2018", "", "Oct 18 2018", "Nov 10 2018"]}
df = pd.DataFrame(start_data)
df["beg"] = pd.to_datetime(df["beg"])
df["end"] = pd.to_datetime(df["end"])
起点:
person_id nid beg end
0 1 1 2018-01-01 2018-02-01
1 1 2 2018-01-05 2018-03-04
2 1 3 2018-01-10 NaT
3 1 4 2018-02-05 2018-10-18
4 2 1 2018-01-25 2018-11-10
目标输出:
person_id date 1 2 3 4
1 2018-01-01 1 0 0 0
1 2018-01-05 1 1 0 0
1 2018-01-10 1 1 1 0
1 2018-02-01 0 1 1 0
1 2018-02-05 0 1 1 1
1 2018-03-04 0 0 1 1
1 2018-10-18 0 0 1 0
2 2018-01-25 1 0 0 0
2 2018-11-10 0 0 0 0
我正在尝试将所有活动的nid
与关联的person_id
绑定在一起,然后将根据最近的date
(少于日期的活动列)将其连接到另一个数据框。最后,这将成为预测模型输入的一部分。
执行类似pd.get_dummies(df["nid"])
的操作,得到以下输出:
1 2 3 4
0 1 0 0 0
1 0 1 0 0
2 0 0 1 0
3 0 0 0 1
4 1 0 0 0
因此,需要将其移至代表生效日期的其他索引,并按person_id
分组,然后进行汇总以匹配目标输出。
向任何能提出适当利用Dask方法的人提供特殊奖励。由于可伸缩性,这就是我们在流水线的其他部分使用的东西。这可能是个白日梦,但我想我会把它寄出去,看看会回来什么。
答案 0 :(得分:2)
这个问题很难,我只能想到numpy
广播来加快for循环
s=df.set_index('person_id')[['beg','end']].stack()
l=[]
for x , y in df.groupby('person_id'):
y=y.fillna({'end':y.end.max()})
s1=y.beg.values
s2=y.end.values
t=s.loc[x].values
l.append(pd.DataFrame(((s1-t[:,None]).astype(float)<=0)&((s2-t[:,None]).astype(float)>0),columns=y.nid,index=s.loc[[x]].index))
s=pd.concat([s,pd.concat(l).fillna(0).astype(int)],1).reset_index(level=0).sort_values(['person_id',0])
s
Out[401]:
person_id 0 1 2 3 4
beg 1 2018-01-01 1 0 0 0
beg 1 2018-01-05 1 1 0 0
beg 1 2018-01-10 1 1 1 0
end 1 2018-02-01 0 1 1 0
beg 1 2018-02-05 0 1 1 1
end 1 2018-03-04 0 0 1 1
end 1 2018-10-18 0 0 0 0
beg 2 2018-01-25 1 0 0 0
end 2 2018-11-10 0 0 0 0
答案 1 :(得分:1)
类似于@WenYoBen的方法,在广播和返回方面略有不同:
def onehot(group):
pid, g = group
ends = g.end.fillna(g.end.max())
begs = g.beg
days = pd.concat((ends,begs)).sort_values().unique()
ret = pd.DataFrame((days[:,None] < ends.values) & (days[:,None]>= begs.values),
columns= g.nid)
ret['persion_id'] = pid
return ret
new_df = pd.concat([onehot(group) for group in df.groupby('person_id')], sort=False)
new_df.fillna(0).astype(int)
输出:
1 2 3 4 persion_id
0 1 0 0 0 1
1 1 1 0 0 1
2 1 1 1 0 1
3 0 1 1 0 1
4 0 1 1 1 1
5 0 0 1 1 1
6 0 0 0 0 1
0 1 0 0 0 2
1 0 0 0 0 2
答案 2 :(得分:0)
这是一项根据beg_col
和end_col
有效日期范围对数据进行一次热编码的功能。需要注意的一个极端情况是同一target
列的多个开始生效日期。您可以在该函数中添加一些巧妙的过滤器来处理该问题,但是我只在此处保留简单的版本。
def effective_date_range_one_hot_encode(x, beg_col="beg", end_col="end", target="nid"):
pos_change = x.loc[:, [beg_col, target]]
pos_change = pos_change.set_index(beg_col)
pos_change = pd.get_dummies(pos_change[target])
neg_change = x.loc[:, [end_col, target]]
neg_change = neg_change.set_index(end_col)
neg_change = pd.get_dummies(neg_change[target]) * -1
changes = pd.concat([pos_change, neg_change])
changes = changes.sort_index()
changes = changes.cumsum()
return changes
new_df = df.groupby("person_id").apply(effective_date_range_one_hot_encode).fillna(0).astype(int)
new_df.index = new_df.index.set_names(["person_id", "date"])
new_df = new_df.reset_index()
new_df = new_df.dropna(subset=["date"], how="any")
可以使用.groupby()
来应用该功能,如果需要在分布式环境中运行该功能,则可以使用Dask中的.map_partitions()
函数。只需首先将索引设置为您计划groupby
的列,然后创建一个帮助函数以重置索引。
输出
person_id effective_date 1 2 3 4
0 1 2018-01-01 1 0 0 0
1 1 2018-01-05 1 1 0 0
2 1 2018-01-10 1 1 1 0
3 1 2018-02-01 0 1 1 0
4 1 2018-02-05 0 1 1 1
5 1 2018-03-04 0 0 1 1
6 1 2018-10-18 0 0 1 0
8 2 2018-01-25 1 0 0 0
9 2 2018-11-10 0 0 0 0
答案 3 :(得分:0)
对于OP来说有点晚了,但这应该可以帮助其他有此问题的人。我遇到了一个非常类似的问题,并通过以下方式解决了该问题。
OP的原始数据:
start_data = {"person_id": [1, 1, 1, 1, 2], "nid": [1, 2, 3, 4, 1],
"beg": ["Jan 1 2018", "Jan 5 2018", "Jan 10 2018", "Feb 5 2018", "Jan 25 2018"],
"end": ["Feb 1 2018", "Mar 4 2018", "", "Oct 18 2018", "Nov 10 2018"]}
df = pd.DataFrame(start_data)
df["beg"] = pd.to_datetime(df["beg"])
df["end"] = pd.to_datetime(df["end"])
建议的解决方案:
from dateutil.rrule import rrule, DAILY
# Create an empty df which we'll append the results to
months_df = pd.DataFrame( columns= ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
'july', 'aug', 'sep', 'oct', 'nov', 'dec'])
# Create function to loop through a list and remove any dates that occured before a certain date
def remove_dates(date_range, date_range2):
for i in range(0,len(date_range)):
if date_range[i] > datetime.datetime(2017,12,31):
date_range2.append(date_range[i])
return date_range2
months = [1,2,3,4,5,6,7,8,9,10,11,12] # this is used in the list comprehension
for i in range(0, len(df)):
# Return list of weeks that are in each date range (i.e. weeks between "Day of Start Date" and "Day of End Date")
date_range = [dt for dt in rrule(DAILY, dtstart=df.loc[:,'beg'][i],\
until=df.loc[:,'end'][i])]
# Remove any dates that occurred before some arbitrary cutoff
date_range2 = []
date_range = remove_dates(date_range, date_range2)
months_list = set([date.month for date in date_range]) # Return unique months
months_list = [elem in months_list for elem in months] # Check which months of the year are present in the date range
# Append results to months_df
months_df = months_df.append(pd.DataFrame(months_list,\
index=['jan', 'feb', 'mar', 'apr', 'may', 'jun',
'july', 'aug', 'sep', 'oct', 'nov', 'dec']).T, ignore_index=False)
df = df.join(months_df.reset_index(drop=True)) # Merge the two dfs
输出
person_id nid beg end jan feb mar apr may \
0 1 1 2018-01-01 2018-02-01 True True False False False
1 1 2 2018-01-05 2018-03-04 True True True False False
2 1 3 2018-01-10 NaT True True True True True
3 1 4 2018-02-05 2018-10-18 False True True True True
4 2 1 2018-01-25 2018-11-10 True True True True True
jun july aug sep oct nov dec
0 False False False False False False False
1 False False False False False False False
2 True True True True True True True
3 True True True True True False False
4 True True True True True True False
评论: