Совокупный счет с Specifi c Состояние в PANDAS - PullRequest
0 голосов
/ 29 февраля 2020

У меня есть набор организационных данных с несколькими годами на организацию. Я просмотрел множество кумулятивных суммированных и групповых ответов на StackOverflow и, похоже, не могу найти тот, который соответствует моей ситуации.

Я заинтересован в подсчете совокупного количества лет, в течение которых каждая организация имела НОВУЮ активную программу. Программа обозначена значениями «1» в столбце Программа . Я хочу получить новый столбец Years_NEW_Program , показанный ниже.

OrgID   Year    Program     Years_NEW_Program       event_window
3128    2015    0           0                       -2
3128    2016    0           0                       -1
3128    2017    1           1                       0
3128    2018    1           2                       1
11502   2015    1           0                       
11502   2016    1           0
31530   2009    0           0                       -2  
31530   2010    0           0                       -1
31530   2011    1           1                       0   
31530   2012    1           2                       1   
31530   2013    1           3                       2   
31530   2014    0           0
99      2014    1           0     
99      2015    0           0
99      2016    1           0   
99      2017    0           0
99      2018    0           0

Что делает его уникальным, так это то, что я хочу, чтобы «подсчет» начинался только тогда, когда организация НЕ имела программу в предыдущие годы (как указано «0» в Программа ), а затем реализует его (как указано '1' в Program ). Я также хочу начать подсчет только в том случае, если у организации было по крайней мере за два года из '0's до запуска программы и она сохраняется в течение как минимум двух лет - это почему ID 99 выше не получает счет.

В идеале , я бы также смог получить столбец, подобный этому, в event_window для организаций, которые получают ненулевые значения в Years_NEW_Program . Но в случае необходимости я мог бы работать только с Years_NEW_Program.

Спасибо за любую помощь!

Ответы [ 2 ]

1 голос
/ 03 марта 2020

@ braml1 ответ работает, но здесь я предоставил альтернативу, которая подправляет несколько вещей. Во-первых, вот альтернативное решение:

df = df.sort_values(by=['OrgID', 'Year'], ascending = True)
df['startCounter'] = df.groupby('OrgID')['Program'].apply(lambda x: 
                          ((x.shift(1)==0)&(x.shift(2) == 0) & (x == 1))).values
df['stopCounter'] = df.groupby('OrgID')['Total_Fees_for_Services_binary'].apply(lambda x: x==0).values
df['counting'] = np.where(df['startCounter'] & ~df['stopCounter'],1,np.NaN)
df['counting'] = np.where(df['stopCounter'], 0, df['counting'])
df['counting'] = df.groupby('OrgID')['counting'].ffill().fillna(0) 
a = df.groupby('OrgID')['counting'].fillna(0).eq(1)
b = a.cumsum()
df['cumsum'] = b-b.where(~a).ffill().fillna(0).astype(int)

Вот ключевые отличия. Сначала я сортирую по OrgID и Год :

df = df.sort_values(by=['OrgID', 'Year'], ascending = True)

Затем с startCounter и stopCounter Я различаюсь на включая groupby операторы:

df['startCounter'] = df.groupby('OrgID')['Program'].apply(lambda x: 
                      ((x.shift(1)==0)&(x.shift(2) == 0) & (x == 1))).values
df['stopCounter'] = df.groupby('OrgID)['Total_Fees_for_Services_binary'].apply(lambda x: x==0).values

С помощью этих команд я могу пропустить создание двухэтапных промежуточных переменных program1Ybefore и program2Ybefore .

Далее, первые две строки в создании переменной count такие же, как в ответе @ braml1:

df['counting'] = np.where(df['startCounter'] & ~df['stopCounter'],1,np.NaN)
df['counting'] = np.where(df['stopCounter'], 0, df['counting'])

Третья строка, тем не менее, снова включает groupby :

df['counting'] = df.groupby('OrgID')['counting'].ffill().fillna(0) 

Однако самое большое изменение наступает на последнем шаге - создание переменной cumsum . Здесь я был вдохновлен другим SO ответом

В частности, вместо применения функции * bra421 cumsumWithReset (которая использует al oop по всем строкам кадра данных ), Я применяю кумулятивную сумму со сбросом при соблюдении определенного c условия. Сначала a преобразует двоичный (0/1) столбец , считая , в столбец True / False. Напомним, что столбец count - это столбец, в котором указаны все строки, в которых есть действительная «новая программа», и именно для этих строк мы хотим получить накопительную сумму.

a = df.groupby('OrgID')['counting'].fillna(0).eq(1)

b затем принимает кумулятивную сумму для значений в a

b = a.cumsum()

Наконец, мы присваиваем значения новой переменной cumsum со значениями b , где выполняется условие a , и ноль в противном случае (и затем заполнение нулями столбца вперед, пока мы не найдем a снова):

df['cumsum'] = b-b.where(~a).ffill().fillna(0).astype(int) 

Именно этот последний шаг действительно помогает производительности. Не выполняя функцию iterrows , входящую в функцию cumsumWithReset , мы можем реально повысить производительность, особенно с большим набором данных.

Опять же, спасибо @ braml1 за помощь. Ваше решение сработало! Мое альтернативное решение - только некоторые постепенные улучшения.

1 голос
/ 29 февраля 2020

Вот (по общему признанию, длительный) способ сделать это. Во-первых, создайте отдельный фрейм данных для каждого OrgID, что облегчит его обработку. Позже вы соединяете их вместе. Для каждого из этих фреймов данных создайте «startCounter» и «stopCounter» в зависимости от вашего состояния. А затем добавьте столбец «подсчет», который должен представлять, когда счетчик должен быть включен. Существует функция, которая вычисляет совокупную сумму с помощью сброса, и с вами должно быть все в порядке.

import pandas as pd
import numpy as np

df = pd.read_csv('file.csv')

def cumsumWithReset(df):
    # Make the cumulative sum of the column "counting"
    # When the value of "counting" is zero, then reset the cumulative sum
    prevVal = 0
    df["cumsum"] = 0
    for index, row in df.iterrows():
        cumsum = row["counting"] + prevVal
        if row['counting'] == 0:
            cumsum = 0
        prevVal = cumsum
        df.loc[index, 'cumsum'] = cumsum
    return df


df = df.sort_values(by="OrgID", ascending = True)
orgList = df.OrgID.drop_duplicates()
dfList = []
for org in orgList:
    dfOrg = df[df["OrgID"] == org]
    dfOrg = dfOrg.sort_values(by="Year", ascending = True).reset_index(drop=True)
    dfOrg['program1Ybefore'] = dfOrg["Program"].shift(periods=1, fill_value = 1)
    dfOrg['program2Ybefore'] = dfOrg["Program"].shift(2, fill_value = 1)
    dfOrg['startCounter'] = (dfOrg['program1Ybefore'] == 0) & (dfOrg['program2Ybefore'] == 0) & (dfOrg['Program'] == 1)
    dfOrg['stopCounter'] =  dfOrg["Program"] == 0
    dfOrg['counting'] =  np.where(dfOrg['startCounter'] & ~dfOrg['stopCounter'],1,np.NaN)
    dfOrg['counting'] =  np.where(dfOrg['stopCounter'],0,dfOrg['counting'])
    dfOrg['counting'] =  dfOrg['counting'].ffill(axis = 0).fillna(0) 
    dfOrg = cumsumWithReset(dfOrg)
    dfList.append(dfOrg)

dfResult = pd.concat(dfList).reset_index(drop=True)

РЕДАКТИРОВАТЬ для большого df: Не l oop для отдельных кадров данных для каждого организации, но создайте другой флаг, который отслеживает изменения организаций.

df = df.sort_values(by=["OrgID", "Year"], ascending = [True, True])
df["newOrg"] = df["OrgID"] != df["OrgID"].shift(1)
df["newOrgShift"] = df["newOrg"].shift(1, fill_value = True)

df['program1Ybefore'] = df["Program"].shift(periods=1, fill_value = 1)
df['program1Ybefore'] = np.where(df["newOrg"],1,df['program1Ybefore'])
df['program2Ybefore'] = df["Program"].shift(2, fill_value = 1)
df['program2Ybefore'] = np.where((df["newOrg"]) | (df["newOrgShift"]) ,1,df['program2Ybefore'])


df['startCounter'] = (df['program1Ybefore'] == 0) & (df['program2Ybefore'] == 0) & (df['Program'] == 1)
df['stopCounter'] =  (df["Program"] == 0) | (df["newOrg"])
df['counting'] =  np.where(df['startCounter'] & ~df['stopCounter'],1,np.NaN)
df['counting'] =  np.where(df['stopCounter'],0,df['counting'])
df['counting'] =  df['counting'].ffill(axis = 0).fillna(0) 

df = cumsumWithReset(df)
...