У меня есть два DataFrames в полярах: один описывает метаданные, а другой — фактические данные (LazyFrames используются, поскольку фактические данные больше):
import polars as pl
df = pl.LazyFrame(
{
"ID": ["CX1", "CX2", "CX3"],
"Sample1": [1, 1, 1],
"Sample2": [2, 2, 2],
"Sample3": [4, 4, 4],
}
)
df_meta = pl.LazyFrame(
{
"sample": ["Sample1", "Sample2", "Sa,mple3", "Sample4"],
"qc": ["pass", "pass", "fail", "pass"]
}
)
Мне нужно выбрать столбцы в df
для образцов, которые прошли проверку qc
, используя информацию в df_meta
. Как видите, у df_meta
есть дополнительная выборка, которая, конечно, нас не интересует, поскольку она не является частью наших данных.
В пандах я бы сделал (не очень элегантно, но выполняет свою работу):
df.loc[:, df.columns.isin(df_meta.query("qc == 'pass'")["sample"])]
Однако я не уверен, как это сделать в полярных условиях. Чтение SO и документации не дало мне однозначного ответа.
Я пробовал:
df.with_context(
df_meta.filter(pl.col("qc") == "pass").select(pl.col("sample").alias("meta_ids"))
).with_columns(
pl.all().is_in("meta_ids")
).collect()
Однако это вызывает исключение:
InvalidOperationError: `is_in` cannot check for String values in Int64 data
Я предполагаю, что он проверяет содержимое столбцов, но меня интересуют имена столбцов.
Я также пробовал:
meta_ids = df_meta.filter(pl.col("qc") == "pass").get_column("sample")
df.select(pl.col(meta_ids))
но, как и ожидалось, возникает исключение, поскольку в первом dataFrame не учтен один образец:
ColumnNotFoundError: Sample4
Каков был бы правильный способ сделать это?
Либо у вас неправильный ввод (Sample4
нет во входном DataFrame), либо вы хотите выбирать только столбцы, которые имеют pass
в df_meta
DataFrame И также существуют в df
DataFrame.
df
passing_cols = df_meta.filter(pl.col("qc") == "pass").select(pl.col.sample).collect().to_series()
df.select(list(set(df.columns) & set(passing_cols))).collect()
┌─────────┬─────────┐
│ Sample1 ┆ Sample2 │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════════╪═════════╡
│ 1 ┆ 2 │
│ 1 ┆ 2 │
│ 1 ┆ 2 │
└─────────┴─────────┘
Предложенное решение правильное, но кажется немного неуклюжим. Более глубокий взгляд на документацию API показал, что функция by_name
из API селекторов имеет параметр (require_all
), который соответствует этой цели:
import polars.selectors as cs
meta_ids = df_meta.filter(pl.col("qc") == "pass").get_column("sample")
print(df.select(cs.by_name(meta_ids, require_all=False)))
shape: (3, 2)
┌─────────┬─────────┐
│ Sample1 ┆ Sample2 │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════════╪═════════╡
│ 1 ┆ 2 │
│ 1 ┆ 2 │
│ 1 ┆ 2 │
└─────────┴─────────┘
Просто обратите внимание: это не работает с входными данными вашего примера - потому что у вас есть LazyFrame в качестве входных данных, поэтому вам, возможно, придется немного изменить входные данные или синтаксис. Вот почему я не использовал get_column
, а использовал collect
одного столбца и вместо него to_series
.
Ах да, я перебрал несколько вариантов, прежде чем остановился на LazyFrame. У меня нет кода передо мной, но я исправлю ответ завтра.
Просто чтобы опираться на https://stackoverflow.com/a/78676922/ - я нахожу require_all=False
довольно загадочным.
Также можно установить пересечение с помощью селектора cs.all()
:
>>> meta_ids = df_meta.filter(pl.col("qc") == "pass")["sample"]
>>> cs.all() & cs.by_name(meta_ids)
(cs.all() & cs.by_name('Sample1', 'Sample2', 'Sample4'))
df.select(cs.all() & cs.by_name(meta_ids))
shape: (3, 2)
┌─────────┬─────────┐
│ Sample1 ┆ Sample2 │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════════╪═════════╡
│ 1 ┆ 2 │
│ 1 ┆ 2 │
│ 1 ┆ 2 │
└─────────┴─────────┘
Просто примечание:
with_context
устарел: github.com/pola-rs/polars/pull/16860