У меня есть один фрейм данных Polars, и я пытаюсь найти различия (поля, значения которых изменились) в нескольких столбцах между группами по одному ключу. В кадре данных может быть несколько групп и более одного столбца. Группы по сути представляют собой дату и время в формате int (ГГГГММДД).
Как я могу найти строки, в которых есть новое значение (любого) столбца в дату?
Образец данных:
raw_df = pl.DataFrame([
{'id': 'AAPL','update_time': 20241112,'status':'trading', 'underlying': 'y'},
{'id': 'MSFT','update_time': 20241113,'status': 'trading', 'underlying': 'x'},
{'id': 'NVDA','update_time': 20241112,'status': 'trading', 'underlying': 'z'},
{'id': 'MSFT','update_time': 20241112,'status': 'pending','underlying': 'x'},
{'id': 'AAPL','update_time': 20241113,'status': 'trading', 'underlying': 'y'},
{'id': 'NVDA','update_time': 20241113,'status': 'trading', 'underlying': 'z'},
{'id': 'TSLA','update_time': 20241112,'status': 'closed', 'underlying': 'v'},
]
)
expected_df = pl.DataFrame([
{'id': 'MSFT','update_time': 20241112,'status':'pending', 'underlying': 'x'},
{'id': 'MSFT','update_time': 20241113,'status': 'trading', 'underlying': 'x'},
]
)
Ниже показано, как выглядит ввод данных.
shape: (7, 4)
┌──────┬─────────────┬─────────┬────────────┐
│ id ┆ update_time ┆ status ┆ underlying │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ str │
╞══════╪═════════════╪═════════╪════════════╡
│ AAPL ┆ 20241112 ┆ trading ┆ y │
│ MSFT ┆ 20241113 ┆ trading ┆ x │
│ NVDA ┆ 20241112 ┆ trading ┆ z │
│ MSFT ┆ 20241112 ┆ pending ┆ x │
│ AAPL ┆ 20241113 ┆ trading ┆ y │
│ NVDA ┆ 20241113 ┆ trading ┆ z │
│ TSLA ┆ 20241112 ┆ closed ┆ v │
└──────┴─────────────┴─────────┴────────────┘
И ожидаемый результат ниже, показывающий идентификатор, время обновления и поле, которое изменилось. Если изменено/обновлено более одного поля, в идеале оно должно перейти в новую строку.
Я пытаюсь найти измененные поля, сгруппированные по «update_time» по ключу «id». Единственное предостережение: могут быть идентификаторы, которые присутствуют в группе, но не присутствуют в другой группе, например «TSLA». Следовательно, эти идентификаторы, которые не являются общими или пересекаются между группами, можно игнорировать. Поскольку статус изменился только у MSFT, его следует отфильтровать по тем двум строкам, в которых он был обновлен. Изменения полей следует выполнять только для всех остальных столбцов, кроме update_time, который мы используем для группировки.
shape: (2, 3)
┌──────┬─────────────┬─────────┐
│ id ┆ update_time ┆ status │
│ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str │
╞══════╪═════════════╪═════════╡
│ MSFT ┆ 20241112 ┆ pending │
│ MSFT ┆ 20241113 ┆ trading │
└──────┴─────────────┴─────────┴
Не смог понять, как это сделать, но это самый близкий из имеющихся у меня, который не может работать с упомянутым ранее предостережением.
def find_updated_field_differences(df):
columns_to_check = [col for col in df.columns if col != 'id' and col != 'update_time']
sorted_df = df.sort('update_time')
grouped_df = sorted_df.groupby(["update_time"])
result_data = []
for group_key, group_df in grouped_df:
print(group_df)
for col in columns_to_check:
group_df = group_df.with_columns(
(pl.col(col) != pl.col(col).shift()).alias(f"{col}_changed")
)
differing_rows = group_df.filter(
pl.any([pl.col(f"{col}_changed") for col in columns_to_check])
)
result_data.append(differing_rows)
differing_df = pl.concat(result_data)
differing_df = differing_df.sort("id")
return differing_df
@Hericks Спасибо, что заметили это, следует сравнивать каждые две группы. Сравнивая с предыдущей датой. Следовательно, можно ожидать появления новой строки в MSFT 20241114 и в ожидании (если эти два изменения являются изменениями). Следует сохранять только те строки, в которых имеются уникальные обновления/изменения. еще раз спасибо
Ваш подход немного усложняет ситуацию. Я предлагаю сначала отсортировать данные по id
и update_time
, а затем сдвинуть данные для подготовки к сравнению. После этого вы сможете определить строки, в которых id
одинаковый, но есть разница:
import polars as pl
def find_updated_field_differences(df):
sorted_df = df.sort(['id', 'update_time'])
shifted_df = sorted_df.shift(-1)
mask = (
(sorted_df['id'] == shifted_df['id']) &
((sorted_df['status'] != shifted_df['status']) | (sorted_df['underlying'] != shifted_df['underlying']))
)
start_changes = sorted_df.filter(mask)
end_changes = sorted_df.shift(-1).filter(mask)
differing_df = pl.concat([start_changes, end_changes]).unique()
return differing_df.sort(['id', 'update_time'])
raw_df = pl.DataFrame([
{'id': 'AAPL', 'update_time': 20241112, 'status': 'trading', 'underlying': 'y'},
{'id': 'MSFT', 'update_time': 20241113, 'status': 'trading', 'underlying': 'x'},
{'id': 'NVDA', 'update_time': 20241112, 'status': 'trading', 'underlying': 'z'},
{'id': 'MSFT', 'update_time': 20241112, 'status': 'pending', 'underlying': 'x'},
{'id': 'AAPL', 'update_time': 20241113, 'status': 'trading', 'underlying': 'y'},
{'id': 'NVDA', 'update_time': 20241113, 'status': 'trading', 'underlying': 'z'},
{'id': 'TSLA', 'update_time': 20241112, 'status': 'closed', 'underlying': 'v'},
])
result_df = find_updated_field_differences(raw_df)
print(result_df)
что дает вам
shape: (2, 4)
┌──────┬─────────────┬─────────┬────────────┐
│ id ┆ update_time ┆ status ┆ underlying │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ str │
╞══════╪═════════════╪═════════╪════════════╡
│ MSFT ┆ 20241112 ┆ pending ┆ x │
│ MSFT ┆ 20241113 ┆ trading ┆ x │
В общем, похоже, что вы хотите сохранить все уникальные строки (игнорируя update_time
) в группе, определенной id
, если в соответствующей группе есть хотя бы две уникальные строки (т. е. произошло изменение).
Этого можно достичь следующим образом.
(
raw_df
.sort("update_time")
.unique(subset=["id", "status", "underlying"], keep = "first", maintain_order=True)
.filter(
pl.len().over("id") > 1
)
)
shape: (2, 4)
┌──────┬─────────────┬─────────┬────────────┐
│ id ┆ update_time ┆ status ┆ underlying │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ str │
╞══════╪═════════════╪═════════╪════════════╡
│ MSFT ┆ 20241112 ┆ pending ┆ x │
│ MSFT ┆ 20241113 ┆ trading ┆ x │
└──────┴─────────────┴─────────┴────────────┘
pl.DataFrame.unique
удаляет повторяющиеся строки. Параметр subset
указывает, какие столбцы следует учитывать при сравнении двух строк как возможные дубликаты.pl.len().over("id")
подсчитывает количество уникальных строк в каждой группе, определенной id
.Сортировка по update_time
и установка параметров keep = "first", maintain_order=True
при вызове pl.DataFrame.unique
не являются обязательными. Я использую его, чтобы гарантировать сохранение первого (во времени) появления каждой уникальной строки (для каждой группы, определенной id
).
В Polars есть специальная функция, упрощающая проверку col != col.shift()
.
Кодирование длины серии: .rle_id()
df.with_columns(
pl.exclude("id", "update_time").rle_id().over("id").name.suffix("_change")
)
shape: (7, 6)
┌──────┬─────────────┬─────────┬────────────┬───────────────┬───────────────────┐
│ id ┆ update_time ┆ status ┆ underlying ┆ status_change ┆ underlying_change │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ str ┆ u32 ┆ u32 │
╞══════╪═════════════╪═════════╪════════════╪═══════════════╪═══════════════════╡
│ AAPL ┆ 20241112 ┆ trading ┆ y ┆ 0 ┆ 0 │
│ MSFT ┆ 20241113 ┆ trading ┆ x ┆ 0 ┆ 0 │
│ NVDA ┆ 20241112 ┆ trading ┆ z ┆ 0 ┆ 0 │
│ MSFT ┆ 20241112 ┆ pending ┆ x ┆ 1 ┆ 0 │
│ AAPL ┆ 20241113 ┆ trading ┆ y ┆ 0 ┆ 0 │
│ NVDA ┆ 20241113 ┆ trading ┆ z ┆ 0 ┆ 0 │
│ TSLA ┆ 20241112 ┆ closed ┆ v ┆ 0 ┆ 0 │
└──────┴─────────────┴─────────┴────────────┴───────────────┴───────────────────┘
Не уверен, упрощаю ли я ситуацию или нет, но в данном примере кажется, что вы просто хотите проверить, содержат ли какие-либо столбцы изменения.
то есть rle_id > 0
df.filter(
pl.any_horizontal(
(pl.exclude("id", "update_time").rle_id() > 0).any().over("id")
)
)
shape: (2, 4)
┌──────┬─────────────┬─────────┬────────────┐
│ id ┆ update_time ┆ status ┆ underlying │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ str ┆ str │
╞══════╪═════════════╪═════════╪════════════╡
│ MSFT ┆ 20241113 ┆ trading ┆ x │
│ MSFT ┆ 20241112 ┆ pending ┆ x │
└──────┴─────────────┴─────────┴────────────┘
Спасибо, это самое элегантное решение. Но странно, что у меня возникает невоспроизводимая ошибка, которая показывает, что «поток <unnamed>» запаниковал из-за «неудавшегося утверждения: (left == right)
» левая и правая части должны иметь одинаковую длину», D:\bld\polars_1690884875212_build_env\.cargo\git\ checkouts\arrow2-8a2ad61d97265680\d5c78e7\src\compute\boolean_kleene.rs:24:5 Похоже на внутреннюю ошибку, я не слишком знаком с Rust. Эта ошибка (или нет) иногда случается в одной и той же схеме, поэтому я озадачен. Тем не менее, это решение работает с упрощенными данными.
Определенно внутренняя ошибка — посмотрим, смогу ли я воспроизвести ее на более крупном примере.
Мне не удалось воспроизвести 0.20.22-rc.1
— если вам удастся создать воспроизводимый пример, сообщите об ошибке. github.com/pola-rs/polars/issues
Для меня вопрос не совсем ясен. Каков ожидаемый результат, если столбец меняется между двумя значениями (один и тот же идентификатор, разные даты)? Ожидаем ли мы, что будут сохранены все строки или только две строки с двумя уникальными значениями?