панды используют cumsum по столбцам, но сбрасывают счетчик

Это пост и этот пост приблизили меня, но я не смог чтобы решить мою проблему.

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

     2017-04-03    2017-04-04    2017-04-05    2017-04-06
id                                                                         
0           0.0        active           0.0           0.0   
1           0.0        active           0.0        active   
2           0.0           0.0           0.0           0.0 

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

Для приведенного выше df выходной df будет выглядеть так:

     2017-04-03    2017-04-04    2017-04-05    2017-04-06
id                                                                         
0    inactive_1        active    inactive_1    inactive_2   
1    inactive_1        active    inactive_1        active   
2    inactive_1    inactive_2    inactive_3    inactive_4

эта функция очень близка мне, но не учитывает сброс общей суммы, она просто суммирует все экземпляры нуля в строке.

def inactive(s):
     np.where(s == 0, 'inactive_' + (s.eq(0).cumsum()).astype(str), s)

df.apply(inactive, 1)

person Matt    schedule 18.12.2017    source источник
comment
Что такое количество индексов и количество столбцов в реальных данных?   -  person jezrael    schedule 18.12.2017
comment
~100 000 строк на ~300 столбцов. Решение @coldspeed ниже работает хорошо   -  person Matt    schedule 19.12.2017
comment
@Мэтт ура. Вы можете голосовать за все ответы, даже если вы можете принять только один.   -  person cs95    schedule 19.12.2017
comment
Я сравниваю решения и с моими примерными данными и 100000, 300 строками, столбцами - cᴏʟᴅsᴘᴇᴇᴅ решение в 3.5 раза медленнее, чем me;), проверьте время   -  person jezrael    schedule 19.12.2017


Ответы (2)


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

def f(x):
    return x.groupby(x.ne(x.shift()).cumsum()).cumcount() + 1

i = df.apply(pd.to_numeric, errors='coerce')
j = 'inactive_' + i.apply(f, axis=1).astype(str)

df[:] = np.where(i.ne(0), df.values, j)

df

    2017-04-03  2017-04-04  2017-04-05  2017-04-06
id                                                
0   inactive_1      active  inactive_1  inactive_2
1   inactive_1      active  inactive_1      active
2   inactive_1  inactive_2  inactive_3  inactive_4
person cs95    schedule 18.12.2017

Вы можете использовать:

#convert to numeric, NaNs for non numeric
df1 = df.apply(pd.to_numeric, errors='coerce')
#count consecutive values with reset
a = df1 == 0
b = a.cumsum(axis=1)
c = b-b.where(~a, axis=1).ffill(axis=1).fillna(0).astype(int)

print (c)
    2017-04-03  2017-04-04  2017-04-05  2017-04-06
id                                                
0            1           0           1           2
1            1           0           1           0
2            1           2           3           4


#replace by mask 
df = df.mask(c != 0, 'inactive_' + c.astype(str))
print (df)
    2017-04-03  2017-04-04  2017-04-05  2017-04-06
id                                                
0   inactive_1      active  inactive_1  inactive_2
1   inactive_1      active  inactive_1      active
2   inactive_1  inactive_2  inactive_3  inactive_4

Время:

np.random.seed(425)
df = pd.DataFrame(np.random.choice([0, 'active'], size=(100000, 300)))

In [4]: %timeit (jez(df))
1 loop, best of 3: 1min 40s per loop

In [5]: %timeit col(df)
1 loop, best of 3: 5min 54s per loop

def jez(df):
    df1 = df.apply(pd.to_numeric, errors='coerce')
    #count consecutive values
    a = df1 == 0
    b = a.cumsum(axis=1)
    c = b-b.where(~a, axis=1).ffill(axis=1).fillna(0).astype(int)
    #replace by mask 
    return df.mask(c != 0, 'inactive_' + c.astype(str))

def f(x):
    return x.groupby(x.ne(x.shift()).cumsum()).cumcount() + 1

def col(df):

    i = df.apply(pd.to_numeric, errors='coerce')
    j = 'inactive_' + i.apply(f, axis=1).astype(str)

    df[:] = np.where(i.ne(0), df.values, j)

    return(df)

Предупреждение:

Производительность действительно зависит от данных.

person jezrael    schedule 18.12.2017
comment
Эти сроки не являются окончательными. Это действительно будет зависеть от данных. Я надеюсь, что данные OP имеют широкий формат с большим количеством столбцов, чем строк, поэтому вы увидите лучшую производительность. - person cs95; 18.12.2017
comment
Я также могу добавить тайминги для него, но apply + groupby очень медленный, поэтому я думаю, что он тоже будет медленнее. - person jezrael; 18.12.2017
comment
Действительно, но разница, вероятно, была бы меньше, чем сейчас ;) - person cs95; 18.12.2017
comment
Хм, вот что я подумал. Возможно, стоит придумать решение, которое сначала переносит данные, а затем выполняет все эти операции, а затем OP может использовать то, что лучше, в зависимости от их фактических данных. - person cs95; 18.12.2017
comment
Я тестирую, но In [82]: %timeit (jez(df.T).T) 1 loop, best of 3: 6.9 s per loop - person jezrael; 18.12.2017
comment
Нет, я не думаю, что это сработает. Попробуйте на образце. Возможно, нужен другой ответ. - person cs95; 18.12.2017