Подсчитайте одинаковые последовательные числа в столбце списка в фрейме данных Polars

У меня есть pl.DataFrame со столбцом, содержащим списки с целыми числами. Мне нужно утверждать, что каждое последовательное целое число появляется максимум два раза подряд.

Например, список, содержащий [1,1,0,-1,1], подойдет, так как число 1 появляется максимум два раза подряд (первые два элемента, за которыми следует ноль).

Этот список должен привести к неверному утверждению: [1,1,1,0,-1] Число 1 появляется три раза подряд.

Вот игрушечный пример, где row2 должно приводить к ошибочному утверждению.

import polars as pl

row1 = [0, 1, -1, -1, 1, 1, -1, 0]
row2 = [1, -1, -1, -1, 0, 0, 1, -1]
df = pl.DataFrame({"list": [row1, row2]})
print(f"row1: {row1}")
print(f"row2: {row2}")
print(df)


row1: [0, 1, -1, -1, 1, 1, -1, 0]
row2: [1, -1, -1, -1, 0, 0, 1, -1]
shape: (2, 1)
┌───────────────┐
│ list          │
│ ---           │
│ list[i64]     │
╞═══════════════╡
│ [0, 1, … 0]   │
│ [1, -1, … -1] │
└───────────────┘

🤔 А знаете ли вы, что...
Python поддерживает динамическую типизацию, что облегчает разработку.


2
50
3

Ответы:

Вам наверняка понадобится метод для последовательной проверки чисел и фильтрации Dataframe оттуда.

Что-то вроде:

def check_consecutive_repeats(list):
    count = 1
    prev = list[0]
    
    for i in range(1, len(list)):
        if list[i] == prev:
            count += 1
            if count > 2:
                return False
        else:
            count = 1
            prev = list[i]
    
    return True

Это будет перебирать список и проверять, повторяется ли данный символ более 2 раз, если да, то он вернет False.

Отсюда вы можете решить, что делать со списком, который не удался.

Другие решения вы также можете найти здесь.


Решено

Можно использовать следующее.

  1. Выполните кодирование серий списка с помощью pl.Expr.rle. Это создает список структур. Каждая структура содержит (уникальное) значение списка и соответствующую длину выполнения.

  2. Проверьте, не превышает ли максимальная длина пробега в списке 2.

  3. Убедитесь, что результат имеет тип bool, выбрав первый (и единственный) элемент в результирующем списке (используя pl.Expr.list.first).

df.with_columns(
    ok=pl.col("list").list.eval(
        pl.element().rle().struct.field("len").max() <= 2
    ).list.first()
)
shape: (2, 2)
┌──────────────────────────────┬───────┐
│ list                         ┆ ok    │
│ ---                          ┆ ---   │
│ list[i64]                    ┆ bool  │
╞══════════════════════════════╪═══════╡
│ [0, 1, -1, -1, 1, 1, -1, 0]  ┆ true  │
│ [1, -1, -1, -1, 0, 0, 1, -1] ┆ false │
└──────────────────────────────┴───────┘

Выполнение всего этого в pl.Expr.list.eval можно избежать, развернув список. Затем необходима оконная функция ( pl.Expr.over), чтобы обеспечить вычисление максимума отдельно для каждого списка.

max_run_length = pl.col("list").explode().rle().struct.field("len").max().over(pl.int_range(pl.len()))
df.with_columns(passed=max_run_length <= 2)

Результат будет тот же.


Использование map_elements с генератором и коротким замыканием:

N = 2

df.with_columns(
    valid=pl.col('list').map_elements(
        lambda x: all(
            any(x[i] != x[i+1:i+N+1]) for i in range(len(x)-N+1)
        ), return_dtype=pl.Boolean
    )
)

Выход:

┌───────────────┬───────┐
│ list          ┆ valid │
│ ---           ┆ ---   │
│ list[i64]     ┆ bool  │
╞═══════════════╪═══════╡
│ [0, 1, … 0]   ┆ true  │
│ [1, -1, … -1] ┆ false │
└───────────────┴───────┘