Эффективный диапазон дат с одним горячим кодированием - PullRequest
6 голосов
/ 30 мая 2019

Начиная с этого примера данных ...

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"]) get это вывод:

   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 .Это то, что мы используем для других частей конвейера из-за масштабируемости.Это может быть несбыточной мечтой, но я подумал, что отправлю это, чтобы посмотреть, что вернется.

Ответы [ 3 ]

2 голосов
/ 30 мая 2019

Вопрос сложный, я могу думать только о 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 голос
/ 31 мая 2019

Аналогично подходу @ 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
0 голосов
/ 28 июня 2019

Здесь представлена ​​функция, которая выполняет горячее кодирование данных на основе действующего диапазона дат 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(), и если вам нужно, чтобы она выполнялась в распределенной среде, вы можете использовать функцию .map_partitions() в Dask. Просто установите индекс в столбце, который вы планируете 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
...