Я пытаюсь найти лучший способ заполнить столбец в DataFrame, основанный на значениях в комбинации оставшихся столбцов.
Я хочу создать столбец v2, так что каждый раз, когда 1 встречается в v1, предыдущие 3 дат и дата, когда 1 столкнулась, для одного и того же ID, должны быть заполнены 1s в новом столбце.
Это то, что DataFrame будет выглядеть первоначально:
date id v1
0 2017-05-29 5206 0
1 2017-05-30 5206 0
2 2017-05-31 5206 0
3 2017-06-01 5206 0
4 2017-06-02 5206 0
5 2017-06-03 5206 0
6 2017-06-04 5206 1
7 2017-05-29 5207 0
8 2017-05-30 5207 1
9 2017-05-31 5207 0
10 2017-06-01 5207 1
11 2017-06-02 5207 0
12 2017-06-03 5207 0
13 2017-06-04 5207 0
14 2017-05-29 5208 0
15 2017-05-30 5208 1
16 2017-05-31 5208 0
17 2017-06-01 5208 0
18 2017-06-02 5208 0
19 2017-06-03 5208 0
20 2017-06-04 5208 1
И я хочу:
date id v1 v2
0 2017-05-29 5206 0 0
1 2017-05-30 5206 0 0
2 2017-05-31 5206 0 0
3 2017-06-01 5206 0 1
4 2017-06-02 5206 0 1
5 2017-06-03 5206 0 1
6 2017-06-04 5206 1 1
7 2017-05-29 5207 0 1
8 2017-05-30 5207 1 1
9 2017-05-31 5207 0 1
10 2017-06-01 5207 1 1
11 2017-06-02 5207 0 0
12 2017-06-03 5207 0 0
13 2017-06-04 5207 0 0
14 2017-05-29 5208 0 1
15 2017-05-30 5208 1 1
16 2017-05-31 5208 0 0
17 2017-06-01 5208 0 1
18 2017-06-02 5208 0 1
19 2017-06-03 5208 0 1
20 2017-06-04 5208 1 1
Код для воссоздания исходного образца DataFrame в Python приведен ниже:
import pandas as pd
import datetime
base = datetime.datetime.today()-datetime.timedelta(days=500)
df=pd.DataFrame()
df['date']=3*[base + datetime.timedelta(days=x) for x in range(0, 7)]
df['date']=df['date'].dt.floor('d')
df['id']=sorted(7*list(range(5206,5209)))
df['v1']=[0,0,0,0,0,0,1,
0,1,0,1,0,0,0,
0,1,0,0,0,0,1]
Также обратите внимание, что я не возражаю, если это делается с использованием R, так как у меня нет предпочтения между R и Python для этой задачи.
С R и data.table:
library(data.table)
setDT(DF)
DF[, v := do.call(pmax, shift(v1, 0:3, type="lead", fill=0L)), by=id]
date id v1 v2 v
1: 2017-05-29 5206 0 0 0
2: 2017-05-30 5206 0 0 0
3: 2017-05-31 5206 0 0 0
4: 2017-06-01 5206 0 1 1
5: 2017-06-02 5206 0 1 1
6: 2017-06-03 5206 0 1 1
7: 2017-06-04 5206 1 1 1
8: 2017-05-29 5207 0 1 1
9: 2017-05-30 5207 1 1 1
10: 2017-05-31 5207 0 1 1
11: 2017-06-01 5207 1 1 1
12: 2017-06-02 5207 0 0 0
13: 2017-06-03 5207 0 0 0
14: 2017-06-04 5207 0 0 0
15: 2017-05-29 5208 0 1 1
16: 2017-05-30 5208 1 1 1
17: 2017-05-31 5208 0 0 0
18: 2017-06-01 5208 0 1 1
19: 2017-06-02 5208 0 1 1
20: 2017-06-03 5208 0 1 1
21: 2017-06-04 5208 1 1 1
date id v1 v2 v
Как это работает: shift
с типом "свинец" смотрит вперед, в этом случае на расстояния 0, 1, 2 или 3 (с неопределенными значениями заменяется на ноль). pmax
ищет по величине максимальное значение по этим векторам.
Аналогично, из комментария @RyanD:
DF[order(date), v :=
do.call(pmax, shift(v1, 0:3, type="lead", fill=0L))
, by=id]
Это имеет то преимущество, что оно работает, даже если данные не сортируются по date
. Он сортирует данные временно только при построении столбца.
В качестве альтернативы выполните скользящее соединение:
DF[, date := as.IDate(date)] # format
DF[, v := DF[v1 == 1][.SD, on=.(id, date), roll=-3, .N, by=.EACHI]$N]
Это имеет то преимущество, что оно работает, даже если перечисление дат является неполным. Он просматривает каждую строку DF
в DF[v1 == 1]
, считая любое соответствие 0-3 дня в будущем.
Данные:
DF = structure(list(date = c("2017-05-29", "2017-05-30", "2017-05-31",
"2017-06-01", "2017-06-02", "2017-06-03", "2017-06-04", "2017-05-29",
"2017-05-30", "2017-05-31", "2017-06-01", "2017-06-02", "2017-06-03",
"2017-06-04", "2017-05-29", "2017-05-30", "2017-05-31", "2017-06-01",
"2017-06-02", "2017-06-03", "2017-06-04"), id = c(5206L, 5206L,
5206L, 5206L, 5206L, 5206L, 5206L, 5207L, 5207L, 5207L, 5207L,
5207L, 5207L, 5207L, 5208L, 5208L, 5208L, 5208L, 5208L, 5208L,
5208L), v1 = c(0L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 1L, 0L, 1L, 0L,
0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 1L), v2 = c(0L, 0L, 0L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L
)), .Names = c("date", "id", "v1", "v2"), row.names = c(NA, -21L
), class = "data.frame")
Решение от панд, с bfill
и limit
df.v1.where(df.v1==1).groupby(df['id']).bfill(3).fillna(0)
Out[223]:
0 0.0
1 0.0
2 0.0
3 1.0
4 1.0
5 1.0
6 1.0
7 1.0
8 1.0
9 1.0
10 1.0
11 0.0
12 0.0
13 0.0
14 1.0
15 1.0
16 0.0
17 1.0
18 1.0
19 1.0
20 1.0
Name: v1, dtype: float64
#df['v2']=df.v1.where(df.v1==1).groupby(df['id']).bfill(3).fillna(0)