En commençant par cet exemple de données ...
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
Point de départ:
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
Sortie de l'objectif:
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
de lier tous les nid
actifs au person_id
associé person_id
Cela sera alors joint à un autre dataframe basé sur la dernière date
inférieure à une colonne d'activité datée. Et enfin, cela fera partie de l'entrée dans un modèle prédictif.
Faire quelque chose comme pd.get_dummies(df["nid"])
obtient cette sortie:
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"])
Cela doit donc être déplacé vers un autre index représentant la date d'effet, groupé par person_id
, puis agrégé pour correspondre à la sortie de l'objectif.
Bonus spécial pour quiconque peut proposer une approche qui tirerait correctement parti de Dask . C'est ce que nous utilisons pour d'autres parties du pipeline en raison de l'évolutivité. C'est peut-être une chimère, mais j'ai pensé l'envoyer pour voir ce qui reviendrait.
4 Réponses :
La question est difficile, je ne peux penser numpy
diffusion numpy
pour accélérer la boucle 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
Similaire à l'approche de @ WenYoBen, un peu différente dans la diffusion et le retour:
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
Production:
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)
Voici une fonction qui encode à chaud les données en fonction d'une beg_col
end_col
effective beg_col
et end_col
. Un cas particulier à rechercher est celui de plusieurs dates de début de validité pour la même colonne target
. Vous pouvez ajouter un filtrage intelligent à la fonction pour gérer cela, mais je vais simplement garder la version simple ici.
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
La fonction peut être appliquée en utilisant .groupby()
et si vous avez besoin de l' .map_partitions()
dans un environnement distribué, vous pouvez utiliser la fonction .map_partitions()
dans Dask. Définissez d'abord votre index sur la colonne que vous prévoyez de groupby
puis créez une fonction d'assistance pour réinitialiser l'index.
Production
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")
Un peu tard pour l'OP mais cela devrait aider les autres qui ont ce problème. Je suis tombé sur un problème très similaire et je l'ai résolu de la manière suivante.
Données originales de l'OP:
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
Solution proposée:
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
Production
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"])
Commentaires: