Pandas постепенно вычитает дату, пока в фрейме данных не будет выполнено условие

#python #pandas #loops #timedelta

#python #pandas #циклы #timedelta

Вопрос:

У меня есть фрейм данных, который выглядит следующим образом:

 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') amp; (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
  

Ответ №1:

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


 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
  

Ответ №2:

Я предполагаю, что 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) amp; (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)    
  

Когда вы печатаете свой фрейм данных, вы получите:

        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
  

Ответ №3:

Мы можем создать простой цикл с 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
  

Комментарии:

1. ваш метод с диапазоном 2 работает с указанными данными, но если дата, например 20190613 , не будет, вам нужен как минимум диапазон 4 или 5, я думаю, поскольку интервал между датами, который вы просматриваете, составляет один месяц

2. В Op конкретно указано вычитать дважды. Вот почему я использовал диапазон. Но мы можем добавить 5 на всякий случай..

3. Я вижу, я понимаю, что OP не хочет никакой даты между этими двумя границами, и операция должна выполняться до тех пор. Просто интерпретация вопроса 🙂

Ответ №4:

Вы можете сделать это, написав функцию, а затем применив ее к столбцу даты. 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