Подсчет последовательных элементов в списке Pandas

Я создал следующий фрейм данных pandas:

import pandas as pd
import numpy as np

ds = {'col1':[1,"S",3,4,"S"], 'col2' : [6,"S",8,9,"S"],'col3' : [67,None,87,79,"S"]}

df = pd.DataFrame(data=ds)

df['col4']= df[['col1','col2','col3']].values.tolist()

Кадр данных выглядит следующим образом:

print(df)

  col1 col2  col3          col4
0    1    6    67    [1, 6, 67]
1    S    S  None  [S, S, None]
2    3    8    87    [3, 8, 87]
3    4    9    79    [4, 9, 79]
4    S    S     S     [S, S, S]

Для каждой записи мне нужно посчитать количество последовательных букв «S» внутри col4. Результирующий фрейм данных будет выглядеть так:

  col1 col2  col3          col4   iCount
0    1    6    67    [1, 6, 67]      0
1    S    S  None  [S, S, None]      2
2    3    8    87    [3, 8, 87]      0
3    4    9    79    [4, 9, 79]      0
4    S    S     S     [S, S, S]      3

Я попробовал этот код:

col4 = np.array(df['col4'])
iCount = 0
for i in range(len(df)):
    for j in range(len(col4[i])):

        if (col4[i][j] == "S"):
            iCount += 1
    
        else:
            iCount = 0

df['iCount'] = iCount

Но я получаю следующий фрейм данных:

  col1 col2  col3          col4  iCount
0    1    6    67    [1, 6, 67]       3
1    S    S  None  [S, S, None]       3
2    3    8    87    [3, 8, 87]       3
3    4    9    79    [4, 9, 79]       3
4    S    S     S     [S, S, S]       3

Пожалуйста, может кто-нибудь помочь мне найти ошибку?

Каким должен быть результат, если у вас есть список типа ['S', 'S', None, 'S']? и почему?

mozway 17.06.2024 09:31

Мозвей, ты всегда на высоте! Результатов будет 2 (я смотрю на максимальную последовательность). Только что увидел ваш ответ: это работает. Еще раз спасибо !

Giampaolo Levorato 17.06.2024 10:45
5
2
114
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Я бы использовал itertools.groupby:

from itertools import groupby

def consec(lst):
    return max((len(list(g)) for k,g in
                groupby(lst, lambda x: x=='S') if k), default=0)

df['iCount'] = df['col4'].map(consec)

Примечание. используя max здесь, чтобы получить самую длинную последовательность, поскольку может быть более одного отрезка S, но вы можете использовать min/sum или любую другую логику.

Если вы уверены, что в каждом списке содержится максимум одна серия S, вы можете упростить:

df['iCount'] = [sum(x=='S' for x in lst) for lst in df['col4']]

Выход:

  col1 col2  col3          col4  iCount
0    1    6    67    [1, 6, 67]       0
1    S    S  None  [S, S, None]       2
2    3    8    87    [3, 8, 87]       0
3    4    9    79    [4, 9, 79]       0
4    S    S     S     [S, S, S]       3

Просто используйте значения столбцов.

iCount_list = []
for row in df['col4']:
    count = 0
    consecutive_s = 0
    for value in row:
        if value == "S":
            consecutive_s += 1
            count = max(count, consecutive_s)
        else:
            consecutive_s = 0
    iCount_list.append(count)

df['iCount'] = iCount_list

Это даст:

col1 col2  col3          col4  iCount
0    1    6    67    [1, 6, 67]       0
1    S    S  None  [S, S, None]       2
2    3    8    87    [3, 8, 87]       0
3    4    9    79    [4, 9, 79]       0
4    S    S     S     [S, S, S]       3
count=0
list1=[]
for i in df["col4"]:
    for j in i:
        if j= = "S":
            count +=1
    print(count)
    list1.append(count)
    count=0
df['count']=list1

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

Добро пожаловать в ТАК! Пожалуйста, подтвердите свой ответ результатами, полученными с использованием предоставленных входных данных.

OCa 17.06.2024 10:20

Один подход!

df['iCount'] = df['col4'].apply(lambda x: (series:=pd.Series(x)).groupby(series.ne(series.shift()).cumsum().iloc[series[series= = "S"].index]).cumcount().max() + 1).fillna(0)

Как функция:

def func(x):
    series = pd.Series(x)
    groups = series.ne(series.shift()).cumsum().iloc[series[series == "S"].index]
    out = series.groupby(groups).cumcount().max() + 1
    return out
                                                     
df['iCount'] = df['col4'].apply(func).fillna(0)

Выход:

   col1 col2 col3          col4  iCount
0     1    6   67    [1, 6, 67]     0.0
1  None    S    S  [None, S, S]     2.0
2     3    8   87    [3, 8, 87]     0.0
3     4    9   79    [4, 9, 79]     0.0
4     S    S    S     [S, S, S]     3.0

Используйте pd.Series.groupby и отфильтруйте элементы, равные "S", а затем получите эти индексы для фильтрации групп по cumsum и cumcount.

IMO, использование панд здесь не имеет смысла, это делает код примерно в 1000 раз медленнее, чем использование чистого Python.

mozway 17.06.2024 12:44

Для максимального последовательного появления "S" вы можете попробовать следующее:

s = df['col4'].explode()= = "S"
df['iCount'] = pd.Series(
    np.where(
        s,
        s.groupby(s.ne(s.shift()).cumsum()).cumcount() + 1,
        0,
    ),
    index = s.index).groupby(level=0).agg(max)

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

Или вот так:

s = df['col4'].explode()= = "S"
df['iCount'] = (s.groupby((s!=s.shift()).cumsum()).transform('size')*s).groupby(level=0).agg(max)

Чтобы просто подсчитать количество S в списках, вы можете попробовать это:

df['iCount'] = df['col4'].apply(pd.Series.value_counts)['S'].fillna(0)

Идея здесь состоит в том, чтобы использовать функцию value_counts для каждой строки для указанного столбца, фильтровать по требуемой строке (в вашем случае это 'S') и заполнять 0 для строк, не встречающихся.

Краткое примечание: 1 преобразование в серию здесь довольно дорогое (лучше используйте collections.Counter), 2 – учитывая разъяснения ОП, это не сработает, поскольку требуется подсчет последовательных S (а не общего количества). ['S', 'S', None, 'S'] должен вернуться 2.

mozway 17.06.2024 12:43

Спасибо @mozway, я отредактировал ваш комментарий, полностью пропустил последующую часть. Что касается вашего первого комментария, я немного запутался. Я не конвертирую в серии, а использую функцию value_counts. За кулисами он создает еще один DataFrame, который, согласитесь, может быть довольно дорогим. Итак, df['col4'].apply(pd.Series.value_counts) вычисляет каждое уникальное значение.

Vardan Grigoryants 17.06.2024 14:09
import pandas as pd

df = pd.DataFrame({
    'col1': ['1', 'S', '3', '4', 'S'],
    'col2': ['6', 'S', '8', '9', 'S'],
    'col3': ['67', None, '87', '79', 'S'],
    'col4': [[1, 6, 67], ['S', 'S', None], [3, 8, 87], [4, 9, 79], ['S', 'S', 'S']]
})

df['S_count'] = df['col4'].apply(lambda col : pd.Series(col).eq('S').cumsum()
                                 .mul(pd.Series(col).eq('S'))
                                 .max() )
                                                            
print(df)     
'''
 col1 col2  col3          col4  S_count
0    1    6    67    [1, 6, 67]        0
1    S    S  None  [S, S, None]        2
2    3    8    87    [3, 8, 87]        0
3    4    9    79    [4, 9, 79]        0
4    S    S     S     [S, S, S]        3
'''                                                       

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