скользящий счет уникальных значений за год по группам в pandas - PullRequest
3 голосов
/ 10 января 2020

Итак, у меня есть следующий фрейм данных:

Period      group     ID    
20130101     A        10
20130101     A        20
20130301     A        20
20140101     A        20
20140301     A        30
20140401     A        40

20130101     B        11
20130201     B        21
20130401     B        31
20140401     B        41
20140501     B        51

Мне нужно посчитать, сколько разных ID существует на group за последний год. Поэтому мой желаемый результат будет выглядеть следующим образом:

Period      group     num_ids_last_year
20130101     A            2 # ID 10 and 20 in the last year
20130301     A            2 
20140101     A            2 
20140301     A            2 # ID 30 enters, ID 10 leaves
20140401     A            3 # ID 40 enters

20130101     B            1
20130201     B            2
20130401     B            3
20140401     B            2 # ID 11 and 21 leave 
20140501     B            2 # ID 31 leaves, ID 51 enters

Период в формате datetime. Я перепробовал много вещей в духе:

df.groupby(['group','Period'])['ID'].nunique() # Get number of IDs by group in a given period.
df.groupby(['group'])['ID'].nunique() # Get total number of IDs by group.

df.set_index('Period').groupby('group')['ID'].rolling(window=1, freq='Y').nunique()

Но последнее даже невозможно. Есть ли простой способ сделать это? Я думаю, может быть какая-то комбинация cumcount() и pd.DateOffset или, может быть, ge(df.Period - dt.timedelta(365), но я не могу найти ответ.

Спасибо.

Редактировать: добавили факт, что я могу найти более одного ID в данном Period

Ответы [ 3 ]

1 голос
/ 21 января 2020

глядя на вашу структуру данных, я предполагаю, что у вас есть МНОГИЕ дубликаты , поэтому начните с удаления их. drop_duplicates имеют тенденцию быть быстрым

Я предполагаю, что df['Period'] столбцы имеют тип dtype datetime64[ns]

df = df.drop_duplicates()
results = dict()
for start in df['Period'].drop_duplicates():
    end = start.date() - relativedelta(years=1)
    screen = (df.Period <= start) & (df.Period >= end)  # screen for 1 year of data
    singles = df.loc[screen, ['group', 'ID']].drop_duplicates()  # screen for same year ID by groups
    x = singles.groupby('group').count()
    results[start] = x
results = pd.concat(results, 0)


results
                  ID
           group    
2013-01-01 A       2
           B       1
2013-02-01 A       2
           B       2
2013-03-01 A       2
           B       2
2013-04-01 A       2
           B       3
2014-01-01 A       2
           B       3
2014-03-01 A       2
           B       1
2014-04-01 A       3
           B       2
2014-05-01 A       3
           B       2

это быстрее?

пс, если df['Period'] не дата и время:

df['Period'] = pd.to_datetime(df['Period'],format='%Y%m%d', errors='ignore')
0 голосов
/ 22 января 2020

Здесь решение с использованием groupby и rolling. Примечание: ваш желаемый выход составляет год от YYYY0101 до следующего года YYYY0101, поэтому вам нужно перевести 366D вместо 365D

df['Period'] = pd.to_datetime(df.Period, format='%Y%m%d')
df = df.set_index('Period')

df_final = (df.groupby('group')['ID'].rolling(window='366D')
                                     .apply(lambda x: np.unique(x).size, raw=True)
                                     .reset_index(name='ID_count')
                                     .drop_duplicates(['group','Period'], keep='last'))

Out[218]:
   group     Period  ID_count
1      A 2013-01-01       2.0
2      A 2013-03-01       2.0
3      A 2014-01-01       2.0
4      A 2014-03-01       2.0
5      A 2014-04-01       3.0
6      B 2013-01-01       1.0
7      B 2013-02-01       2.0
8      B 2013-04-01       3.0
9      B 2014-04-01       2.0
10     B 2014-05-01       2.0

Примечание : на 18M + строк, я не думаю, что это решение сделает это в 10 минут. Я надеюсь, что это займет около 30 минут.

0 голосов
/ 11 января 2020
from dateutil.relativedelta import relativedelta
df.sort_values(by=['Period'], inplace=True) # if not already sorted

# create new output df
df1 = (df.groupby(['Period','group'])['ID']
       .apply(lambda x: list(x))
       .reset_index())
df1['num_ids_last_year'] = df1.apply(lambda x: len(set(df1.loc[(df1['Period'] >= x['Period']-relativedelta(years=1)) & (df1['Period'] <= x['Period']) & (df1['group'] == x['group'])].ID.apply(pd.Series).stack())), axis=1)
df1.sort_values(by=['group'], inplace=True)
df1.drop('ID', axis=1, inplace=True)
df1 = df1.reset_index(drop=True)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...