Процент событий до и после последовательности нулей в pandas строках - PullRequest
4 голосов
/ 06 января 2020

У меня есть кадр данных, подобный следующему:

        ID      0   1   2   3   4   5   6   7   8   ... 81  82  83  84  85  86  87  88  89  90  total
-----------------------------------------------------------------------------------------------------
0       A       2   21  0   18  3   0   0   0   2   ... 0   0   0   0   0   0   0   0   0   0    156
1       B       0   20  12  2   0   8   14  23  0   ... 0   0   0   0   0   0   0   0   0   0    231
2       C       0   38  19  3   1   3   3   7   1   ... 0   0   0   0   0   0   0   0   0   0     78
3       D       3   0   0   1   0   0   0   0   0   ... 0   0   0   0   0   0   0   0   0   0      5

, и я хочу знать% событий (чисел в ячейках) до и после того, как первая последовательность нулей длины n появляется в каждом строка. Эта проблема началась с другого вопроса, найденного здесь: Длина первой последовательности нулей заданного размера после определенного столбца в pandas dataframe , и я пытаюсь изменить код, чтобы сделать то, что мне нужно, но я продолжаю получать ошибки и не могу найти правильный путь. Это то, что я пробовал:

def func(row, n):
    """Returns the number of events before the 
    first sequence of 0s of length n is found
    """

    idx = np.arange(0, 91)

    a = row[idx]
    b = (a != 0).cumsum()
    c = b[a == 0]
    d = c.groupby(c).count()

    #in case there is no sequence of 0s with length n
    try:
        e = c[c >= d.index[d >= n][0]]
        f = str(e.index[0])
    except IndexError:
        e = [90]
        f = str(e[0])

    idx_sliced = np.arange(0, int(f)+1)
    a = row[idx_sliced]

    if (int(f) + n > 90):
        perc_before = 100
    else:
        perc_before = a.cumsum().tail(1).values[0]/row['total']

    return perc_before

Как есть, я получаю ошибку:

---> perc_before = a.cumsum().tail(1).values[0]/row['total']
TypeError: ('must be str, not int', 'occurred at index 0')

Наконец, я бы применил эту функцию к кадру данных и возвратил новый столбец с % событий перед первой последовательностью n 0 в каждой строке, например:

        ID      0   1   2   3   4   5   6   7   8   ... 81  82  83  84  85  86  87  88  89  90  total  %_before
---------------------------------------------------------------------------------------------------------------
0       A       2   21  0   18  3   0   0   0   2   ... 0   0   0   0   0   0   0   0   0   0    156   43
1       B       0   20  12  2   0   8   14  23  0   ... 0   0   0   0   0   0   0   0   0   0    231   21
2       C       0   38  19  3   1   3   3   7   1   ... 0   0   0   0   0   0   0   0   0   0     78   90
3       D       3   0   0   1   0   0   0   0   0   ... 0   0   0   0   0   0   0   0   0   0      5   100

Если вы пытаетесь решить эту проблему, вы можете проверить, используя этот пример ввода:

a = pd.Series([1,1,13,0,0,0,4,0,0,0,0,0,12,1,1])
b = pd.Series([1,1,13,0,0,0,4,12,1,12,3,0,0,5,1])
c = pd.Series([1,1,13,0,0,0,4,12,2,0,5,0,5,1,1])
d = pd.Series([1,1,13,0,0,0,4,12,1,12,4,50,0,0,1])
e = pd.Series([1,1,13,0,0,0,4,12,0,0,0,54,0,1,1])

df = pd.DataFrame({'0':a, '1':b, '2':c, '3':d, '4':e})
df = df.transpose()

Ответы [ 4 ]

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

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

  ID  0   1   2   3  4  5   6   7  8  total
0  A  2  21   0  18  3  0   0   0  2     46
1  B  0   0  12   2  0  8  14  23  0     59
2  C  0  38  19   3  1  3   3   7  1     75
3  D  3   0   0   1  0  0   0   0  0      4

Теперь, что я думаю, это команда цепочки для создания маски и поиска, где данные не равны 0, затем используйте cumsum вдоль оси столбца и посмотрите, где diff вдоль столбца равно 0. Чтобы найти первый, вы можете использовать cummax, чтобы считать все столбцы после (по строкам) True. Маскируйте исходный кадр данных противоположностью этой маски, суммируйте по столбцам и делите на общее количество. например, при n = 2:

n=2
df['%_before'] = df[~(df.ne(0).cumsum(axis=1).diff(n, axis=1)[range(9)]
                        .eq(0).cummax(axis=1))].sum(axis=1)/df.total
print (df)
  ID  0   1   2   3  4  5   6   7  8  total  %_before
0  A  2  21   0  18  3  0   0   0  2     46  0.956522
1  B  0   0  12   2  0  8  14  23  0     59  0.000000
2  C  0  38  19   3  1  3   3   7  1     75  1.000000
3  D  3   0   0   1  0  0   0   0  0      4  0.750000

В вашем случае вам нужно изменить range(9) на range(91), чтобы получить все ваши столбцы

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

Попробуйте:

def percent_before(row, n, ncols):
    """Return the percentage of activities happen before
    the first sequence of at least `n` consecutive 0s
    """
    start_index, i, size = 0, 0, 0
    for i in range(ncols):
        if row[i] == 0:
            # increase the size of the island
            size += 1
        elif size >= n:
            # found the island we want
            break
        else:
            # start a new island
            # row[start_index] is always non-zero
            start_index = i
            size = 0

    if size < n:
        # didn't find the island we want
        return 1
    else:
        # get the sum of activities that happen
        # before the island
        idx = np.arange(0, start_index + 1).astype(str)
        return row.loc[idx].sum() / row['total']

df['percent_before'] = df.apply(percent_before, n=3, ncols=15, axis=1)

Результат:

   0  1   2  3  4  5  6   7  8   9  10  11  12  13  14  total  percent_before
0  1  1  13  0  0  0  4   0  0   0   0   0  12   1   1     33        0.454545
1  1  1  13  0  0  0  4  12  1  12   3   0   0   5   1     53        0.283019
2  1  1  13  0  0  0  4  12  2   0   5   0   5   1   1     45        0.333333
3  1  1  13  0  0  0  4  12  1  12   4  50   0   0   1     99        0.151515
4  1  1  13  0  0  0  4  12  0   0   0  54   0   1   1     87        0.172414

Для полного кадра позвоните apply с ncols=91.

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

Другое возможное решение:

def get_vals(df, n):
    df, out = df.T, []
    for col in df.columns:
        diff_to_previous = df[col] != df[col].shift(1)
        g = df.groupby(diff_to_previous.cumsum())[col].agg(['idxmin', 'size'])

        vals = df.loc[g.loc[g['size'] >= n, 'idxmin'].values, col]
        if len(vals):
            out.append( df.loc[np.arange(0, vals[vals == 0].index[0]), col].sum() / df[col].sum() )
        else:
            out.append( 1.0 )
    return out

df['percent_before'] = get_vals(df, n=3)
print(df)

Отпечатки:

   0  1   2  3  4  5  6   7  8   9  10  11  12  13  14  percent_before
0  1  1  13  0  0  0  4   0  0   0   0   0  12   1   1        0.454545
1  1  1  13  0  0  0  4  12  1  12   3   0   0   5   1        0.283019
2  1  1  13  0  0  0  4  12  2   0   5   0   5   1   1        0.333333
3  1  1  13  0  0  0  4  12  1  12   4  50   0   0   1        0.151515
4  1  1  13  0  0  0  4  12  0   0   0  54   0   1   1        0.172414
0 голосов
/ 06 января 2020

Вы можете сделать это, используя метод rolling.

Для вашего примера ввода, учитывая, что число нулей равно 5, мы можем использовать

df.rolling(window=5, axis=1).apply(lambda x : np.sum(x))

Вывод будет выглядеть как

    0   1   2   3     4     5     6     7     8     9    10    11    12    13  \
0 NaN NaN NaN NaN  15.0  14.0  17.0   4.0   4.0   4.0   4.0   0.0  12.0  13.0   
1 NaN NaN NaN NaN  15.0  14.0  17.0  16.0  17.0  29.0  32.0  28.0  16.0  20.0   
2 NaN NaN NaN NaN  15.0  14.0  17.0  16.0  18.0  18.0  23.0  19.0  12.0  11.0   
3 NaN NaN NaN NaN  15.0  14.0  17.0  16.0  17.0  29.0  33.0  79.0  67.0  66.0   
4 NaN NaN NaN NaN  15.0  14.0  17.0  16.0  16.0  16.0  16.0  66.0  54.0  55.0   

     14  
0  14.0  
1   9.0  
2  12.0  
3  55.0  
4  56.0  

Глядя на вывод, очень легко увидеть, что в первом ряду для столбца 11, поскольку значение равно 0, это означает, что начиная с позиции 7, у вас есть 5 нулей. Поскольку ни в одной из других строк нет 0, это означает, что ни в одной из других строк нет 5 смежных нулей.

...