6
votes

Groupe de codage à chaud à plage de dates d'effet

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.


0 commentaires

4 Réponses :


2
votes

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


0 commentaires

1
votes

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)


0 commentaires

0
votes

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")


0 commentaires

0
votes

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:

  • J'ai inclus une fonction remove_dates. C'était parce que je voulais exclure les dates antérieures à une limite arbitraire. Par exemple, je regardais les données pour l'année 2019, mais certains contrats auraient pu commencer en 2018 - je voulais exclure ces mois en 2018 du comptage en 2019. Cette fonction accomplit cela.
  • Le paramètre «DAILY» doit être analysé et modifié en fonction du cas d'utilisation
  • Pour l'observation avec NaT comme date de fin, j'ai considéré que c'était VRAI pour chaque mois. Je ne savais pas à 100% comment le PO voulait que cela soit géré. Si l'utilisateur souhaite gérer cela différemment, je définirais toutes les valeurs vides comme une date explicite pour éviter tout résultat inattendu


0 commentaires