Столбец Pandas, где каждое значение зависит от другого запроса df

#python #python-3.x #pandas #dataframe #aggregate

#python #python-3.x #pandas #фрейм данных #агрегировать

Вопрос:

Я столкнулся со сложной проблемой. У меня есть первый фрейм данных, в котором у меня есть клиенты (обратите внимание, что идентификатор клиента не уникален, у вас может быть тот же идентификатор клиента, связанный с другим TestDate) :

df1 :

 ClientID  TestDate
1A        2019-12-24
1B        2019-08-26
1B        2020-01-12
  

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

df2 :

 LineNumber  ClientID  Date          Amount
1           1A        2020-01-12    50
2           1A        2019-09-24    15
3           1A        2019-12-25    20
4           1A        2018-12-30    30
5           1B        2018-12-30    60
6           1B        2019-12-12    40
  

Я хочу добавить в df1 столбец, содержащий среднее значение и количество строк, но принимающий только строки df2, в которых Date < TestDate

Например, для клиента 1A я возьму только номера строк 2 и 4 (поскольку даты строк 1 и 3 позже, чем TestDate), а затем получу следующий вывод для df1 :

Ожидаемый df1 :

 ClientID  TestDate      NumberOp  MeanOp
1A        2019-12-24    2         22.5
1B        2019-08-26    1         60
1B        2020-01-12    2         50
  

Примечание : С первой строкой клиента 1B, поскольку TestDate является 2019-08-26 , отображается только одна операция (операция lineNumber 6 выполняется в 2019-12-12 , поэтому ПОСЛЕ TestDate, поэтому я не принимаю это во внимание).

У меня уже есть код для этого, но я должен использовать iterrows на моем df1 , что занимает целую вечность :

Текущий код (рабочий, но длинный) :

 for index, row in df1.iterrows():
    id = row['ClientID']
    date = row['TestDate']
    df2_known = df2.loc[df2['ClientID'] == id]
    df2_known = df2_known.loc[df2_known['Date'] < date]
    df1.loc[index, 'NumberOp'] = df2_known.shape[0]
    df1.loc[index, 'MeanOp'] = df2_known['Amount'].mean()
  

У меня была идея использовать агрегаты и такие команды, как mean и count , но тот факт, что я должен фильтровать по дате для каждой строки, является огромной проблемой, которую я не могу понять.
Заранее большое спасибо за помощь.

Редактировать: оставшаяся проблема :

Исправление, указанное в редактировании ответа («на случай, если вы хотите сохранить недостающие совпадающие ключи df2»), не соответствует моей проблеме.

На самом деле, я хочу избежать потери эквивалентной строки df1, если никакая операция в df2 не может быть использована для вычисления среднего значения и подсчета. Я покажу вам проблему на примере :

 df = df2.merge(df1, on=['ClientID'], how='right')
print(df[df['ClientID'] == '5C'])

Output :
ClientID  TestDate    Date          Amount
5C        2019-12-12  2020-01-12    50     
  

Если я выполню groupby и transform , как указано в ответе, в моем выводе не будет строки с CliendID == '5C' , потому что Date < TestDate и Date is null никогда не происходит, поэтому строка теряется, когда я делаю df = df[(df['Date']<df['TestDate']) | (df['Date'].isnull())] . Я лично хочу, чтобы в моем конечном выводе была строка с CliendID == '5C' , которая выглядела бы так :

 ClientID  TestDate      NumberOp  MeanOp
5C        2019-12-12    0         NaN
  

Ответ №1:

Вы можете объединять и преобразовывать:

 df = df2.merge(df1, on=['ClientID'])
#filter based on condition
df = df[df['Date']<df['TestDate']]
#get the mean and count into new columns
df['MeanOp'] = df.groupby(['ClientID'])['Amount'].transform('mean')
df['NumberOp'] = df.groupby(['ClientID'])['Amount'].transform('count')
#drop duplicates and irrelevant columns
df = df.drop(['Amount','Date','LineNumber'],1).drop_duplicates()
  

вывод:

   ClientID    TestDate  MeanOp  NumberOp
1       1A  2019-12-24    22.5         2
4       1B  2019-08-26    70.0         1
  

РЕДАКТИРОВАТЬ: в случае, если вы хотите сохранить недостающие совпадающие ключи df2 :

 df = df2.merge(df1, on=['ClientID'], how='right')
df = df[(df['Date']<df['TestDate']) | (df['Date'].isnull())]
df['MeanOp'] = df.groupby(['ClientID'])['Amount'].transform('mean')
df['NumberOp'] = df.groupby(['ClientID'])['Amount'].transform('count')
df = df.drop(['Amount','Date','LineNumber'],1).drop_duplicates()
  

пример:

 df1:

  ClientID    TestDate
0       1A  2019-12-24
1       1B  2019-08-26
2       1C  2019-08-26

output:

  ClientID    TestDate  MeanOp  NumberOp
1       1A  2019-12-24    22.5         2
4       1B  2019-08-26    70.0         1
5       1C  2019-08-26     NaN         0
  

ОБНОВЛЕНИЕ: на основе редактирования в сообщении, если вы хотите сгруппировать их по (Client_ID, TestDate) :

 df = df2.merge(df1, on=['ClientID'], how='right')
df = df[(df['Date']<df['TestDate']) | (df['Date'].isnull())]
df['MeanOp'] = df.groupby(['ClientID','TestDate'])['Amount'].transform('mean')
df['NumberOp'] = df.groupby(['ClientID','TestDate'])['Amount'].transform('count')
df = df.drop(['Amount','Date','LineNumber'],1).drop_duplicates()
  

вывод:

 df1
  ClientID    TestDate
0       1A  2019-12-24
1       1B  2019-08-26
2       1B  2020-01-12
3       1C  2019-08-26

df2
   LineNumber ClientID        Date  Amount
0           1       1A  2020-01-12      50
1           2       1A  2019-09-24      15
2           3       1A  2019-12-25      20
3           4       1A  2018-12-30      30
4           5       1B  2018-12-30      60
5           6       1B  2019-12-12      40

df
  ClientID    TestDate  MeanOp  NumberOp
1       1A  2019-12-24    22.5         2
4       1B  2019-08-26    60.0         1
6       1B  2020-01-12    50.0         2
8       1C  2019-08-26     NaN         0
  

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

1. @BeamsAdept Добро пожаловать. Добавлено редактирование в сообщение, чтобы охватить этот случай.

2. Большое спасибо за ваше решение, оно работает в общем случае. У меня все еще есть несколько проблем, потому что, чтобы задать запрос в SO, я немного упростил проблему, и теперь я снова столкнулся с этой проблемой: мой df1 может фактически иметь несколько раз одного и того же клиента (поэтому ClientID не уникален), но с другой датой тестирования (соответствующей дате, которую мы берем в качестве ссылки для проверки операций).). В этих условиях у вашего метода, похоже, возникают проблемы со слиянием. Это полностью моя вина, поскольку я не уточнил свою основную проблему, извините за это.

3. @BeamsAdept ничего, что нельзя было бы исправить. Однако это должно сработать. В чем, по-видимому, проблема с множественностью Test_Date одного и того же Client_id? и как вы хотите среднее значение / количество? это для client_id или для (client_id, Test_Date)? пожалуйста, расскажите о реальном случае, чтобы мы могли помочь лучше. Спасибо.

4. @BeamsAdept просто groupby (client_idm test_date). Я добавлю редактирование. Обратите внимание, что по вашему определению и новому примеру, дата в df2, которая меньше, чем оба Test_dates для одного и того же client_id в df1, участвует в обоих mean / count соответствующего Test_date. Это то, что вы хотели, или вы хотели разделить интервалы дат с помощью Test_date ?

5. @BeamsAdept Пожалуйста, проверьте обновление в сообщении, чтобы узнать, решает ли оно вашу проблему. Спасибо.