Проверьте следующий элемент в списке в фрейме данных pandas

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

import pandas as pd
import numpy as np

ds = {
      'col1' : 
          [
              ['U', 'U', 'U', 'U', 'U', 1, 0, 0, 0, 'U','U', None],
              [6, 5, 4, 3, 2],
              [0, 0, 0, 'U', 'U'],
              [0, 1, 'U', 'U', 'U'],
              [0, 'U', 'U', 'U', None]
              ]
      }

df = pd.DataFrame(data=ds)

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

print(df)
                                      col1
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]
1                          [6, 5, 4, 3, 2]
2                          [0, 0, 0, U, U]
3                          [0, 1, U, U, U]
4                       [0, U, U, U, None]

Для каждой строки в col1 мне нужно проверить, за каждым элементом, равным U в списке (слева направо), следует какое-либо значение, кроме U и None: в этом случае я бы создал новый столбец (называемый iCount) со значением 1. Иначе 0.

В приведенном выше примере результирующий фрейм данных будет выглядеть следующим образом:

                                      col1 iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]      1
1                          [6, 5, 4, 3, 2]      0
2                          [0, 0, 0, U, U]      0
3                          [0, 1, U, U, U]      0
4                       [0, U, U, U, None]      0

Только в первой строке за значением U следует значение, которое не является ни U, ни None (это 1)

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

col5 = np.array(df['col1'])

for i in range(len(df)):
    iCount = 0

    for j in range(len(col5[i])-1):
        
        print(col5[i][j])
        
        if ((col5[i][j] == "U") & ((col5[i][j+1] != None) & (col5[i][j+1] != "U"))):
            
            iCount += 1
            
        else:
            iCount = iCount
    

Но я получаю этот (неправильный) фрейм данных:

                                      col1  iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       0
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                       [0, U, U, U, None]       0

Кто-нибудь может мне помочь?

🤔 А знаете ли вы, что...
Python был создан Гвидо ван Россумом и впервые выпущен в 1991 году.


4
80
5

Ответы:

Попробуй это:

def calcUs(lst):
    cnt = 0
    for x, y in zip(lst, lst[1:]):
        if (x == 'U' and y != 'U' and y != None):
            cnt += 1
    return cnt
df['iCount'] = df['col1'].apply(lambda x: calcUs(x))
df

Выход:

                                      col1  iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                       [0, U, U, U, None]       0

Решено

Если вы хотите только проверить, есть ли хотя бы один случай, в котором значение, отличное от None, следует за U, используйте itertools.pairwise и любой:

from itertools import pairwise

def count_after_U(lst):
    return int(any(a=='U' and b not in {'U', None} for a, b in pairwise(lst)))

df['iCount'] = list(map(count_after_U, df['col1']))

Выход:

                                            col1  iCount
0        [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                                [6, 5, 4, 3, 2]       0
2                                [0, 0, 0, U, U]       0
3                                [0, 1, U, U, U]       0
4                             [0, U, U, U, None]       0
5  [U, U, 4, U, U, 1, 0, U, U, None, 1, U, None]       1
6                                [U, None, 1, U]       0

Если вы также хотите проверить другие значения до следующего U, используйте пользовательскую функцию:

def any_after_U(lst):
    flag = False
    for item in lst:
        if item == 'U':
            flag = True
        else:
            if flag and item is not None:
                return 1
    return 0

df['iCount'] = list(map(any_after_U, df['col1']))

Пример:

                                            col1  iCount
0        [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                                [6, 5, 4, 3, 2]       0
2                                [0, 0, 0, U, U]       0
3                                [0, 1, U, U, U]       0
4                             [0, U, U, U, None]       0
5  [U, U, 4, U, U, 1, 0, U, U, None, 1, U, None]       1
6                                [U, None, 1, U]       1

оригинальный ответ до разъяснений

подход 1: рассматриваем только первый пункт после U

IIUC, используйте специальную функцию Python:

from itertools import pairwise

def count_after_U(lst):
    return sum(a=='U' and b not in {'U', None} for a,b in pairwise(lst))

df['iCount'] = list(map(count_after_U, df['col1']))

Или, чтобы быть более гибким с условиями:

def count_after_U(lst):
    flag = False
    iCount = 0
    for item in lst:
        if item == 'U':
            flag = True
        else:
            if flag and item is not None:
                iCount += 1
            flag = False
    return iCount

df['iCount'] = list(map(count_after_U, df['col1']))

Выход:

                                      col1  iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                       [0, U, U, U, None]       0

Более сложный пример:

                                            col1  iCount
0        [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                                [6, 5, 4, 3, 2]       0
2                                [0, 0, 0, U, U]       0
3                                [0, 1, U, U, U]       0
4                             [0, U, U, U, None]       0
5  [U, U, 4, U, U, 1, 0, U, U, None, 1, U, None]       2

подход 2: учитывая все значения после U:

Просто сделайте отступ для флага сброса в предыдущем подходе, чтобы сбросить его только в том случае, если значение еще не найдено:

def count_after_U(lst):
    flag = False
    iCount = 0
    for item in lst:
        if item == 'U':
            flag = True
        else:
            if flag and item is not None:
                iCount += 1
                flag = False
    return iCount

df['iCount'] = list(map(count_after_U, df['col1']))

Пример:

                                            col1  iCount
0        [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                                [6, 5, 4, 3, 2]       0
2                                [0, 0, 0, U, U]       0
3                                [0, 1, U, U, U]       0
4                             [0, U, U, U, None]       0
5  [U, U, 4, U, U, 1, 0, U, U, None, 1, U, None]       3

Код

s = df['col1'].explode()
s1 = s.groupby(level=0).shift(-1)
cond = s.eq('U') & s1.notna() & s1.ne('U')
df['icount'] = cond.groupby(level=0).any().astype('int')

дф

                                      col1  icount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                       [0, U, U, U, None]       0

Вы можете использовать explode, groupby в индексе, а затем агрегировать с суммой, когда условие равно True:

df["iCount"] = (
    df.explode("col1")
    .groupby(level=0)
    .agg(lambda x: ((x == "U") & (~x.shift(-1).isin(["U", None]))).sum())
)
                                      col1  iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                       [0, U, U, U, None]       0

Если вам нужно, чтобы 'iCount' содержал только 0 или 1, а не сумму значений, соответствующих условию, вместо sum вы можете использовать any.astype(int):

df["iCount"] = (
    df.explode("col1")
    .groupby(level=0)
    .agg(lambda x: ((x == "U") & (~x.shift(-1).isin(["U", None]))).any().astype(int))
)
                                      col1  iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                 [0, U, 0, U, 0, U, None]       1

Используйте np.vecorize с функцией и pd.Series:

def func(x):
    lst = pd.Series(x)
    return int(lst[lst.eq('U') & (~lst.shift(-1).isin({'U', None}))].any())

df['iCount'] = np.vectorize(func)(df['col1'])

Или создайте минималистическую функцию с помощью zip.

Похоже, как упоминал ОП, значения могут быть только 1 или 0.

def func(x):
    lst = zip(x, x[1:])
    for a, b in lst:
        if (a == 'U') and (b not in {'U', None}):
            return 1
    return 0

df['iCount'] = np.vectorize(func)(df['col1'])

Выход:

                                      col1  iCount
0  [U, U, U, U, U, 1, 0, 0, 0, U, U, None]       1
1                          [6, 5, 4, 3, 2]       0
2                          [0, 0, 0, U, U]       0
3                          [0, 1, U, U, U]       0
4                       [0, U, U, U, None]       0