Получить первую ненулевую запись для каждого столбца и записать соответствующее значение индекса

У меня есть фрейм данных, который выглядит примерно так:

IndexMonth   Cus1   Cus2  Cus3  Cus4 ........ Cusn
2019-01       0     111    0      0           333
2019-02       0     111    0     666           0
2019-03      500     0     333    55           0  
2019-04      600     0     333    111          0
2019-05      600    100    0      111          0

Я хочу получить первый ненулевой месяц для каждого столбца Cus, а также последний ненулевой месяц. Если у Cus есть перерыв, и он начинается снова, я хочу, чтобы новое начало также было добавлено в колонку начала. Итак, мой вывод должен выглядеть примерно так:

       StartMonth          EndMonth
Cus1   2019-03             2019-05
Cus2   2019-01,2019-05     2019-02,2019-05
Cus3   2019-03             2019-04
Cus4   2019-02             2019-05
..
Cusn   2019-01             2019-01

Не могли бы вы подтвердить, насколько эффективно я могу добиться этого.

да, это 0. (не null или na)

asimo 19.11.2022 11:05
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
1
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сначала я использую функцию транспонирования, затем я получаю первое значение, не равное 0, с помощью idxmax. Я получаю последнее значение с last_valid_index

df2=df.T
df2['first']=df2.ne(0).idxmax(axis=1)
df2['last']=df2[df2.columns[0:5]].mask(df2==0).apply(pd.Series.last_valid_index, axis=1)
#You should set the numbers 0 and 5 according to the number of columns. Here I am only getting the month columns.

print(df2)
'''
IndexMonth  2019-01  2019-02  2019-03  2019-04  2019-05    first     last
Cus1              0        0      500      600      600  2019-03  2019-05
Cus2            111      111        0        0      100  2019-01  2019-05
Cus3              0        0      333      333        0  2019-03  2019-04
Cus4              0      666       55      111      111  2019-02  2019-05
Cusn            333        0        0        0        0  2019-01  2019-01
'''

Первый шаг в порядке. Для второй фазы я пробовал что-то вроде этого, работает с этими данными, но я не уверен, что это будет работать правильно при изменении количества месяцев:

df2['check']=False
for i in range (0,len(df2.index)):
    col_name=df2['first'][i] #which columns is the first ?
    if len(df2.iloc[i].loc[lambda x : x == df2[col_name][i]]) >= 2: #if there is more than one of the same value

        df2['check'][i]=True #return true and fill the first first value with 0. To be able to get the latter when using idxmax.

        df2[col_name][i]=0
df2['first_2']=df2.ne(0).idxmax(axis=1)
df2['is_combine']=(df2['check']==True) & (df2['last'] != df2['first_2'])
df2['StartMonth']=np.where(df2['is_combine']==True,(df2['first'] +', '+df2['last']),df2['first'])
df2['EndMonth']=np.where(df2['is_combine']==True,(df2['first_2'] +', '+df2['last']),df2['last'])
df2=df2[['StartMonth','EndMonth']]
print(df2)
'''
IndexMonth        StartMonth          EndMonth
Cus1                 2019-03           2019-05
Cus2        2019-01, 2019-05  2019-02, 2019-05
Cus3                 2019-03           2019-04
Cus4                 2019-02           2019-05
Cusn                 2019-01           2019-01
'''
Ответ принят как подходящий

Вы можете использовать маски, чтобы сохранить первую/последнюю дату для последовательности ненулевых значений, а затем агрегировать:

df2 = df.set_index('IndexMonth')
m = df2.ne(0)

start = (df2
    .where(m&~m.shift(fill_value=False))
    .stack()
    .reset_index('IndexMonth')
    .groupby(level=0)['IndexMonth']
    .agg(','.join)
    .rename('StartMonth')
 )

end = (df2
    .where(m&~m.shift(-1, fill_value=False))
    .stack()
    .reset_index('IndexMonth')
    .groupby(level=0)['IndexMonth']
    .agg(','.join)
    .rename('EndMonth')
 )

out = pd.concat([start, end], axis=1)

print(out)

Вывод:

           StartMonth         EndMonth
Cus1          2019-03          2019-05
Cus2  2019-01,2019-05  2019-02,2019-05
Cus3          2019-03          2019-04
Cus4          2019-02          2019-05
Cusn          2019-01          2019-01

Спасибо @mozway Не могли бы вы подтвердить, как это работает под капотом?

asimo 20.11.2022 08:35

@asimo конечно. Я конвертирую в логический массив (True, если он не равен нулю), затем сдвигаю вверх или вниз. Первое значение — это True, а shift — это False. И наоборот, последнее значение — True, а следующее — False. Мы используем это для определения дат, а затем агрегируем по каждому клиенту.

mozway 20.11.2022 08:44

Другие вопросы по теме