Панды постепенно вычитают дату, пока не будет выполнено условие на фрейме данных - PullRequest
3 голосов
/ 11 апреля 2019

У меня есть датафрейм, который выглядит так:

Name         Date
Person A     2019-06-18
Person A     2019-05-14
Person A     2019-04-03
Person B     2019-05-19
Person C     2019-05-16
Person C     2019-05-23
Person C     2019-05-15
Person D     2019-06-21

То, что я хочу сделать, это изменить даты тех, кто находится между 5/14 и 6/14 и вычесть 7 дней. Если после завершения этой операции они все еще находятся в этом диапазоне, вычтите еще 7 дней.

В конце я хочу, чтобы данные выглядели так:

Name         Date
Person A     2019-06-18
Person A     2019-05-07
Person A     2019-04-03
Person B     2019-05-12
Person C     2019-05-09
Person C     2019-05-09
Person C     2019-05-08
Person D     2019-06-21

(После этого шага я сверну его так, чтобы у каждого человека была одна строка со всеми датами, а затем отправил бы им информацию - но я думаю, что смогу выяснить это самостоятельно.)

Прямо сейчас у меня следующий код "работает":

df = df[(df['Date'] >= '2019-05-14') & (df['Date'] <= '2019-06-14')]
df['Date'] = df['Date'] - pd.Timedelta(days=7)

Однако я не знаю, как это зациклить, и я также не знаю, как применить это без потери моих исходных данных.

Из-за этого мой код создает такой кадр:

Name         Date
Person A     2019-05-07
Person B     2019-05-12
Person C     2019-05-16
Person C     2019-05-08

Ответы [ 4 ]

3 голосов
/ 11 апреля 2019

Я бы просто подсчитал, сколько раз вам нужно вычесть каждую дату, и сделать это за один шаг


m = df.Date.between('2019-05-14', '2019-06-14')
u = df[m]

d = u.Date - pd.Timestamp('2019-05-13')
o = np.ceil(d.dt.days / 7)

df.loc[m, 'Date'] = df.loc[m, 'Date'] - (o * np.timedelta64(7, 'D'))

       Name       Date
0  Person A 2019-06-18
1  Person A 2019-05-07
2  Person A 2019-04-03
3  Person B 2019-05-12
4  Person C 2019-05-09
5  Person C 2019-05-09
6  Person C 2019-05-08
7  Person D 2019-06-21

Вот версия, которая не изменяет рамку на месте:

m = df.Date.between('2019-05-14', '2019-06-14')
d = df.Date - pd.Timestamp('2019-05-13')

o = np.ceil(d.dt.days / 7)

df.assign(Date=np.where(m, df.Date - (o * np.timedelta64(7, 'D')), df.Date))

       Name       Date
0  Person A 2019-06-18
1  Person A 2019-05-07
2  Person A 2019-04-03
3  Person B 2019-05-12
4  Person C 2019-05-09
5  Person C 2019-05-09
6  Person C 2019-05-08
7  Person D 2019-06-21
1 голос
/ 11 апреля 2019

Я предполагаю, что столбец Date имеет тип datetime64.

Первоначальный шаг заключается в определении «дат границы»:

start_date = pd.to_datetime('2019-05-14')
end_date = pd.to_datetime('2019-06-14')

Затем мы должны определить функциюдля применения к каждой дате:

def fn(dat):
    inRng = (dat >= start_date) & (dat <= end_date)
    dat2 = dat
    if inRng:
        diffWeeks = int((dat - start_date) / np.timedelta64(1, 'W')) + 1
        dat2 -= np.timedelta64(diffWeeks, 'W')
    return dat2

И последний шаг - применить эту функцию.Чтобы упростить сравнение исходных данных и результатов, я решил заменить результат в новом столбце (Dat2):

df['Dat2'] = df.Date.apply(fn)    

Когда вы распечатываете свой DataFrame, вы получите:

       Name       Date       Dat2
0  Person A 2019-06-18 2019-06-18
1  Person A 2019-05-14 2019-05-07
2  Person A 2019-04-03 2019-04-03
3  Person B 2019-05-19 2019-05-12
4  Person C 2019-05-16 2019-05-09
5  Person C 2019-05-23 2019-05-09
6  Person C 2019-05-15 2019-05-08
7  Person D 2019-06-21 2019-06-21
1 голос
/ 11 апреля 2019

Вы можете сделать это, написав функцию, а затем применив ее к столбцу даты. Метод pd.Series.apply работает путем передачи каждого значения в предоставленную функцию. Внутри функции у вас есть простой цикл while, который вычитает 7 дней, пока вы не достигнете желаемого диапазона дат.

from datetime import datetime as dt
from datetime import timedelta

def date_modifier(x):
    d = x
    while True:
        if d >= dt(2019, 5, 14) and d<=dt(2019, 6, 14):
            d-= timedelta(days=7)
        else:
            return d
df['Date-Mod'] = df['Date'].apply(date_modifier)

Дает следующий вывод:

      Name       Date   Date-Mod
0  PersonA 2019-06-18 2019-06-18
1  PersonA 2019-05-14 2019-05-07
2  PersonA 2019-04-03 2019-04-03
3  PersonB 2019-05-19 2019-05-12
4  PersonC 2019-05-16 2019-05-09
5  PersonC 2019-05-23 2019-05-09
6  PersonC 2019-05-15 2019-05-08
7  PersonD 2019-06-21 2019-06-21
1 голос
/ 11 апреля 2019

Мы можем сделать простой цикл с range() и после этого использовать numpy.where, чтобы условно изменить каждую строку, если она находится между двумя датами:

for i in range(2):
    df['Date'] = np.where(df['Date'].between('20190514','20190614'), 
                          df['Date'] - pd.Timedelta(days=7), 
                          df['Date'])

print(df)

       Name       Date
0  Person A 2019-06-18
1  Person A 2019-05-07
2  Person A 2019-04-03
3  Person B 2019-05-12
4  Person C 2019-05-09
5  Person C 2019-05-09
6  Person C 2019-05-08
7  Person D 2019-06-21
...