У меня есть большой фрейм данных Polars, который мне нужен melt
. Этот фрейм данных содержит множество нулевых значений (по крайней мере половину). Я хочу удалить нули при плавлении кадра данных. Я уже пробовал сначала melt
фрейм данных, а затем фильтровать его с помощью drop_nulls()
или аналогичных подходов. Однако это слишком ресурсоемко (на машине с ОЗУ около 1 ТБ).
Есть ли способ отфильтровать набор данных уже в процессе плавления?
Любая помощь приветствуется!
Пример данных.
# in reality, this dataset has about 160k rows and columns
# (square matrix), and is about 100GB
df = {
"A": [None, 2, 3],
"B": [None, None, 2],
"C": [None, None, None],
"names": ["A", "B", "C"]
}
df = pl.DataFrame(df)
df.melt(id_vars = "names", variable_name = "names_2", value_name = "distance")
Выход.
shape: (9, 3)
┌───────┬─────────┬──────────┐
│ names ┆ names_2 ┆ distance │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 │
╞═══════╪═════════╪══════════╡
│ A ┆ A ┆ null │
│ B ┆ A ┆ null │
│ C ┆ A ┆ null │
│ A ┆ B ┆ 2 │
│ B ┆ B ┆ null │
│ C ┆ B ┆ null │
│ A ┆ C ┆ 3 │
│ B ┆ C ┆ 2 │
│ C ┆ C ┆ null │
└───────┴─────────┴──────────┘
Затем это можно было бы отфильтровать (например, с помощью df = df.drop_nulls()
), но я хотел бы получить желаемый результат непосредственно из расплава.
Ожидаемый результат.
shape: (3, 3)
┌───────┬─────────┬──────────┐
│ names ┆ names_2 ┆ distance │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 │
╞═══════╪═════════╪══════════╡
│ A ┆ B ┆ 2 │
│ A ┆ C ┆ 3 │
│ B ┆ C ┆ 2 │
└───────┴─────────┴──────────┘
Я соответствующим образом изменил вопрос. Это квадратная матрица, поэтому она имеет одинаковое количество строк и столбцов. Я просто хочу подчеркнуть, что набор данных большой, поэтому подход «сначала плавление, затем фильтрация» не работает.
Вы пробовали вместо этого использовать LazyFrame
?
В чем преимущество реализации LazyFrame
в этом контексте? Насколько я понимаю, они не оцениваются сразу, но в конечном итоге, когда они будут выполнены, им все равно придется выполнять melt
и drop_nulls
последовательно, не так ли?
Вы можете создать итерацию кадров данных, которые позже можно объединить или обработать отдельно.
pl.concat(
df.filter(pl.col(c).is_not_null()).select(
names = pl.lit(c),
names_2 = pl.col.names,
distance = pl.col(c)
) for c in df.schema.keys() if c != "names"
)
shape: (3, 3)
┌───────┬─────────┬──────────┐
│ names ┆ names_2 ┆ distance │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 │
╞═══════╪═════════╪══════════╡
│ A ┆ B ┆ 2 │
│ A ┆ C ┆ 3 │
│ B ┆ C ┆ 2 │
└───────┴─────────┴──────────┘
Спасибо за хорошее предложение! Кажется, в принципе это работает, однако похоже, что это намного медленнее, чем функция melt
, и это проблема :/ Есть идеи, как сделать это более эффективным?
Скорее всего, вы сможете сделать операцию более эффективной с точки зрения времени выполнения и потребления памяти, используя pl.LazyFrames
и потоковый движок Polars.
Используя pl.LazyFrames , Melt/Unpivot и Filter / drop_nulls не будут выполняться сразу, а сначала агрегироваться в план запроса. При сборе ленивого DataFrame (т. е. материализации его в pl.DataFrame) план запроса можно оптимизировать с учетом последующих операций.
Потоковая передача позволит выполнять обработку не сразу, а пакетно, гарантируя, что обработанные пакеты не вырастут за пределы памяти.
(
df
# convert to pl.LazyFrame
.lazy()
# create query plan
.melt(
id_vars = "names",
variable_name = "names_2",
value_name = "distance"
)
.drop_nulls()
# collect pl.LazyFrame while using streaming engine
.collect(streaming=True)
)
Примечание. Предварительные тесты на моей машине дали значительные улучшения во времени выполнения и потреблении памяти.
Большое спасибо, это звучит идеально! Я еще не проверял его по существу, но на данный момент я приму ответ и отвечу на новый, если у меня возникнут более конкретные проблемы.
Не совсем понятно, что вы подразумеваете под «набор данных содержит около 160 тыс. строк и столбцов». Не могли бы вы уточнить, сколько столбцов имеет DataFrame?