Pandas — подсчитывать разные значения по дате — более эффективный способ?

#python #pandas

#python #pandas

Вопрос:

Новичок здесь…

У меня есть фрейм данных с именем ‘yes_no’, структурированный следующим образом (но в нем около 50 тыс. записей):

       Date        Yes/No
0     2020-10-27     No
1     2020-10-27     No
2     2020-10-26    Yes
3     2020-10-26    Yes
4     2020-10-26    No
5     2020-10-25    No
6     2020-10-25    Yes
7     2020-10-25    No
8     2020-10-24    Yes
9     2020-10-24    Yes
  

Мне нужно подсчитать количество yes и количество nos для каждой даты и вычислить соотношение, чтобы в итоге получилось что-то вроде этого:

      Date        Yes   No  Percentage
0   2020-10-27  1142  120    0.904913
1   2020-10-26  4112  388    0.913778
2   2020-10-25  1055   68    0.939448
3   2020-10-24  1012   86    0.921676
4   2020-10-23  1476  163    0.900549
5   2020-10-22  1633  182    0.899725
6   2020-10-21  1773  237    0.882090
7   2020-10-20  2332  246    0.904577
8   2020-10-19  2868  326    0.897934
9   2020-10-18   892  107    0.892893
10  2020-10-17   992  110    0.900181
11  2020-10-16  2106  207    0.910506
12  2020-10-15  5628  632    0.899042
13  2020-10-14  9304  937    0.908505
14  2020-10-13  8129  881    0.902220
  

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

 by_date = {}
for date in yes_no['Date']:
  by_date[date] = yes_no.loc[yes_no['Date'] == date]


for date in by_date:
  by_date[date] =  by_date[date]['Yes/No'].value_counts()

for date in by_date:
  if 'No' not in by_date[date]:
    by_date[date]['No'] = 0

for date in by_date:
  if 'Yes' not in by_date[date]:
    by_date[date]['Yes'] = 0

for date in by_date:
  by_date[date] = [by_date[date]['Yes'], by_date[date]['No'], (by_date[date]['Yes']/(by_date[date]['Yes']   by_date[date]['No']))]


df_yes = pd.DataFrame(list(by_date.values()),columns = ['Yes', 'No', 'Percentage'])
df_yes['Date'] = list(by_date.keys())
df_yes = df_yes[['Date', 'Yes', 'No', 'Percentage']]
  

Он отлично работал с меньшим фреймом данных (1-2 КБ), но для обработки этого фрагмента кода с 50 тыс. записей требуется вечность:

 for date in yes_no['Date']:
  by_date[date] = yes_no.loc[yes_no['Date'] == date]
  

Должен быть лучший способ сделать это!

Ответ №1:

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

 out = (df.groupby("Date")["Yes/No"]
       .value_counts()
       .unstack(fill_value=0)
       .rename_axis(columns=None)
       .eval("percentage = Yes / (Yes   No)")
      )

print(out)
            No  Yes  percentage
Date                           
2020-10-24   0    2    1.000000
2020-10-25   2    1    0.333333
2020-10-26   1    2    0.666667
2020-10-27   2    0    0.000000
  

Шаги:

  • df.groupby("Date")["Yes/No"] : сгруппируйте наш фрейм данных по «Дате» и выберите столбец «Да / Нет» из этих группировок
  • .value_counts() : Получить количество каждого «да» и «нет» в этом столбце для каждой из этих группировок.
  • .unstack(fill_value=0) : Теперь, когда у нас есть наши подсчеты, мы вносим «Да» и «Нет» в их собственные столбцы.
  • .rename_axis(columns=None) : У нас забавно выглядящее имя индекса столбца, мне лично это не нравится, поэтому я избавляюсь от него.
  • .eval("percentage = Yes / (Yes No)") : Создайте новый столбец с именем percentage и присвоите ему значения всех значений «Да», разделенных на общее количество ответов («Да» «Нет»

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

1. Спасибо — это решило проблему аккуратно и почти мгновенно.

Ответ №2:

 # groupby date and yes/no columns and get the size
# then pivot 
new_df = df.groupby(['Date', 'Yes/No'], as_index=False).size().pivot('Date', 'Yes/No', 'size').replace(np.nan, 0)
# divide the yes column by the size of each group
new_df['percent_yes'] = new_df['Yes'] / new_df.sum(1)
print(new_df)

Yes/No       No  Yes  percent_yes
Date                             
2020-10-24  0.0  2.0     1.000000
2020-10-25  2.0  1.0     0.333333
2020-10-26  1.0  2.0     0.666667
2020-10-27  2.0  0.0     0.000000
  

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

1. использовать unstack(fill_value=0) вместо pivot

2. Вам нужно будет удалить as_index=False из вызова groupby, чтобы использовать unstack(...) метод

Ответ №3:

Вам следует обратить внимание на одноразовое кодирование. pandas использует pd.get_dummies

Мое решение было бы:

 df_new = pd.get_dummies(yes_no, columns=["Yes/No"]).groupby("Date").sum().rename(columns={"Yes/No_No":"No", "Yes/No_Yes":"Yes"}
  

И тогда вы можете легко рассчитать процент.

Ответ №4:

Существует очень простой способ сделать это, возможно, доступны более элегантные способы:

 import pandas as pd

df = pd.DataFrame({'Yes_no': ['yes','no','yes', 'yes', 'no','yes','no','yes', 'yes', 'no','yes','no','yes', 'yes', 'no','yes','no','yes', 'yes', 'no'],
    'Dates': ['2019-07-01','2019-07-01','2019-07-01', '2019-07-03', '2019-07-03','2019-07-03','2019-07-07','2019-07-07', '2019-07-07', '2019-07-07','2019-07-07','2019-07-07','2019-07-07', '2019-07-07', '2019-07-07','2019-07-07','2019-07-07','2019-07-07', '2019-07-07', '2019-07-07']})
dff = df.groupby(['Yes_no','Dates'])['Yes_no'].count()
dff.unstack().T
  

чтобы создать дополнительный столбец с пропорциями, просто определите новый столбец

 dff['prop']=dff['no']/dff['yes']