Python Polars: количество строк с момента последнего значения в группе

Учитывая полярный DataFrame
data = pl.DataFrame({"user_id": [1, 1, 1, 2, 2, 2], "login": [False, True, False, False, False, True]})

Как я могу добавить столбец, который добавляет количество строк с момента последнего входа пользователя в систему, причем для всех строк перед входом в систему для этого пользователя установлено значение «Нет»? Пример вывода для приведенных выше данных:
[None, 0, 1, None, None, 0]

Я пытался адаптировать ответы из здесь, но не смог заставить это работать с группами.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
75
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваш пример немного ограничен. Что произойдет, если для одного пользователя имеется более 1 логина? Вы сказали, что числа должны увеличиваться после последнего входа в систему, а перед первым входом в систему они должны быть «Нет», но что должно произойти между первым и последним входом в систему?

Я предполагаю, что вы хотите сбросить счетчик при каждом входе в систему, поэтому, если я расширяю ваш входной DataFrame:

data = pl.DataFrame({
    "user_id": [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
    "login": [False, True, False, False, True, False, False, False, True, False]
})

┌─────────┬───────┐
│ user_id ┆ login │
│ ---     ┆ ---   │
│ i64     ┆ bool  │
╞═════════╪═══════╡
│ 1       ┆ false │
│ 1       ┆ true  │
│ 1       ┆ false │
│ 1       ┆ false │
│ 1       ┆ true  │
│ 2       ┆ false │
│ 2       ┆ false │
│ 2       ┆ false │
│ 2       ┆ true  │
│ 2       ┆ false │
└─────────┴───────┘

Тогда я ожидаю, что результат будет выглядеть так:

┌─────────┬───────┬──────┐
│ user_id ┆ login ┆ res  │
│ ---     ┆ ---   ┆ ---  │
│ i64     ┆ bool  ┆ u32  │
╞═════════╪═══════╪══════╡
│ 1       ┆ false ┆ null │
│ 1       ┆ true  ┆ 0    │
│ 1       ┆ false ┆ 1    │
│ 1       ┆ false ┆ 2    │
│ 1       ┆ true  ┆ 0    │
│ 2       ┆ false ┆ null │
│ 2       ┆ false ┆ null │
│ 2       ┆ false ┆ null │
│ 2       ┆ true  ┆ 0    │
│ 2       ┆ false ┆ 1    │
└─────────┴───────┴──────┘

В этом случае вы можете использовать Expr.cum_count() поверх Expr.cum_sum() , разделенную на user_id через Expr.over():

(
    data
    .with_columns(
        res = pl.col('login').cum_sum().over('user_id'),
    ).with_columns(
        pl.when('login').then(pl.col('res').cum_count().over('user_id', 'res') - 1),
    )
)

┌─────────┬───────┬──────┐
│ user_id ┆ login ┆ res  │
│ ---     ┆ ---   ┆ ---  │
│ i64     ┆ bool  ┆ u32  │
╞═════════╪═══════╪══════╡
│ 1       ┆ false ┆ null │
│ 1       ┆ true  ┆ 0    │
│ 1       ┆ false ┆ 1    │
│ 1       ┆ false ┆ 2    │
│ 1       ┆ true  ┆ 0    │
│ 2       ┆ false ┆ null │
│ 2       ┆ false ┆ null │
│ 2       ┆ false ┆ null │
│ 2       ┆ true  ┆ 0    │
│ 2       ┆ false ┆ 1    │
└─────────┴───────┴──────┘
Ответ принят как подходящий

Вы можете вычесть номер строки предыдущего входа в систему.

(df.with_row_index()
   .with_columns(distance = 
      pl.col.index - pl.when("login").then("index").forward_fill().over("user_id")
   )
)
shape: (6, 4)
┌───────┬─────────┬───────┬──────────┐
│ index ┆ user_id ┆ login ┆ distance │
│ ---   ┆ ---     ┆ ---   ┆ ---      │
│ u32   ┆ i64     ┆ bool  ┆ u32      │
╞═══════╪═════════╪═══════╪══════════╡
│ 0     ┆ 1       ┆ false ┆ null     │
│ 1     ┆ 1       ┆ true  ┆ 0        │
│ 2     ┆ 1       ┆ false ┆ 1        │
│ 3     ┆ 2       ┆ false ┆ null     │
│ 4     ┆ 2       ┆ false ┆ null     │
│ 5     ┆ 2       ┆ true  ┆ 0        │
└───────┴─────────┴───────┴──────────┘

Если данные не отсортированы, вы можете вместо этого использовать .int_range() в качестве номера строки.

df.with_columns(distance =
   pl.int_range(pl.len()).over("user_id") 
   - pl.when("login").then(pl.int_range(pl.len())).forward_fill().over("user_id")
)

Подробности

(df.with_row_index()
   .with_columns(
      new_index  = pl.when("login").then("index"),
      last_index = pl.when("login").then("index").forward_fill(),
      last_index_groupwise = pl.when("login").then("index").forward_fill().over("user_id")
   )
)
shape: (6, 6)
┌───────┬─────────┬───────┬───────────┬────────────┬──────────────────────┐
│ index ┆ user_id ┆ login ┆ new_index ┆ last_index ┆ last_index_groupwise │
│ ---   ┆ ---     ┆ ---   ┆ ---       ┆ ---        ┆ ---                  │
│ u32   ┆ i64     ┆ bool  ┆ u32       ┆ u32        ┆ u32                  │
╞═══════╪═════════╪═══════╪═══════════╪════════════╪══════════════════════╡
│ 0     ┆ 1       ┆ false ┆ null      ┆ null       ┆ null                 │
│ 1     ┆ 1       ┆ true  ┆ 1         ┆ 1          ┆ 1                    │
│ 2     ┆ 1       ┆ false ┆ null      ┆ 1          ┆ 1                    │
│ 3     ┆ 2       ┆ false ┆ null      ┆ 1          ┆ null                 │
│ 4     ┆ 2       ┆ false ┆ null      ┆ 1          ┆ null                 │
│ 5     ┆ 2       ┆ true  ┆ 5         ┆ 5          ┆ 5                    │
└───────┴─────────┴───────┴───────────┴────────────┴──────────────────────┘
  • когда/то для создания нулевой/ненулевой последовательности
  • .forward_fill(), чтобы перенести предыдущее значение вперед
  • .over() чтобы сделать групповую операцию

это хорошо, forward_fill() прекрасно следует логике этой задачи.

Roman Pekar 13.06.2024 09:52

Спасибо!! Этот ответ хорошо работает, когда user_ids отсортированы, @RomanPekar ваше решение работает, даже если они не отсортированы

cdkdrf 13.06.2024 10:42

@cdkdrf Вместо этого вы можете использовать .int_range() для несортированного кода — это немного более подробно, я добавил пример.

jqurious 13.06.2024 11:04

Благодарить! Не могли бы вы добавить пример того, как я мог бы добавить еще один столбец для количества строк до следующего входа пользователя в систему? Я пробовал с ним поиграться и использовать backward_fill(), но не получилось.

cdkdrf 13.06.2024 11:09

@cdkdrf Может быть, вы можете добавить новый вопрос - он немного другой.

jqurious 13.06.2024 11:14

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