Вот пример, который можно запустить и который демонстрирует проблему. Начальный LazyFrame включает попарные расстояния между точками на плоскости. Поскольку расстояние A->B равно расстоянию B->A, я сохраняю только уникальные пары, а затем создаю метки LazyFrame, чтобы сохранять только уникальные метки. Я соединяю исходный LazyFrame с метками LazyFrame, чтобы получить соответствующие индексы для col_1 и col_2. . Например, в результирующем кадре данных расстояние от значения индекса до того же значения индекса (например, A-A (0->0), B-B (1->1), CC (2->2)) должно быть равно нулю, но это не тот случай, когда LazyFrame не собирается перед объединением.
import polars as pl
import numpy as np
data = {
"col_1": ["A", "A", "A", "B", "B", "B", "C", "C", "C"],
"col_2": ["B", "C", "A", "A", "C", "B", "A", "B", "C"],
"col_3": [1.0, 2.0, 0.0, 1.0, 1.5, 0.0, 2.0, 1.5, 0.0]
}
# Create LazyFrame
pairwise_distances = pl.LazyFrame(data)
# Function to concatenate and sort two elements and return a string with '||'
def concat_and_sort(a, b):
return "||".join(sorted([a, b]))
# Add pairs column and get unique pairs
pairwise_distances = pairwise_distances.with_columns(
pl.struct(["col_1", "col_2"]).map_elements(
lambda x: concat_and_sort(x["col_1"], x["col_2"]),
return_dtype=pl.String
).alias("pairs")
).unique(subset = "pairs").select(pl.col("col_1"), pl.col("col_2"), pl.col("col_3"))
# Concatenate col_1 and col_2, and get unique labels with index
labels = (
pl.concat([
pairwise_distances.select(pl.col("col_1").alias("label")),
pairwise_distances.select(pl.col("col_2").alias("label"))
])
.unique(keep = "first")
.with_row_count(name = "index")
)
# Collect labels LazyFrame to DataFrame
labels_df = labels.collect()
pairwise_distances_df = pairwise_distances.collect()
# Join to get index_1 (without collecting labels)
data_joined_direct = pairwise_distances.join(
labels,
left_on = "col_1",
right_on = "label",
how = "left"
).rename({"index": "index_1"})
# Join to get index_2 (without collecting labels)
data_joined_direct = data_joined_direct.join(
labels,
left_on = "col_2",
right_on = "label",
how = "left"
).rename({"index": "index_2"})
# Select relevant columns (without collecting labels)
result_direct = data_joined_direct.select(["index_1", "index_2", "col_3"])
# Collect the result to execute the lazy operations (without collecting labels first)
# Result without collecting labels first
result_direct_df = result_direct.collect()
# Join to get index_1 (with collecting labels first)
data_joined_collected = pairwise_distances_df.join(
labels_df,
left_on = "col_1",
right_on = "label",
).rename({"index": "index_1"})
# Join to get index_2 (with collecting labels first)
data_joined_collected = data_joined_collected.join(
labels_df,
left_on = "col_2",
right_on = "label",
).rename({"index": "index_2"})
# Select relevant columns (with collecting labels first)
result_collected = data_joined_collected.select(["index_1", "index_2", "col_3"])
Я использую Polars для обработки набора данных, в котором мне нужно создать уникальные метки из двух столбцов, а затем выполнить соединения, чтобы получить индексы для этих меток. Однако я заметил, что если я выполняю соединения непосредственно в LazyFrame, индексы кажутся неправильными. Когда я собираю LazyFrame в DataFrame перед выполнением соединений, индексы верны.
Вот соответствующие фрагменты кода:
import polars as pl
# Assume data is a LazyFrame
data = pl.scan_csv(
source=source_filepath,
separator='\t',
has_header=False,
)
# Concatenate col_1 and col_2, and get unique labels with index
labels = (
pl.concat([
data.select(pl.col("col_1").alias("label")),
data.select(pl.col("col_2").alias("label"))
])
.unique(keep = "first")
.with_row_count(name = "label_index")
)
# Join to get index_1
data = data.join(
labels,
left_on = "col_1",
right_on = "label",
).rename({"label_index": "index_1"})
# Join to get index_2
data = data.join(
labels,
left_on = "col_2",
right_on = "label",
).rename({"label_index": "index_2"})
result = data.select(["index_1", "index_2", "col_3"])
result_df = result.collect()
# Collect labels and data LazyFrame to DataFrame
labels_df = labels.collect()
data_df = data.collect()
# Join to get index_1
data_df = data_df.join(
labels_df,
left_on = "col_1",
right_on = "label",
how = "left"
).rename({"label_index": "index_1"})
# Join to get index_2
data_df = data_df.join(
labels_df,
left_on = "col_2",
right_on = "label",
how = "left"
).rename({"label_index": "index_2"})
result_df = data_df.select(["index_1", "index_2", "col_3"])
Почему существует такое несоответствие между использованием LazyFrame непосредственно для соединений и его сбором перед выполнением соединений? Как я могу обеспечить правильное поведение без необходимости преждевременного сбора LazyFrame?
Будем очень признательны за любую информацию о том, почему это происходит и как это решить!
@jqurious Я отредактировал исходное сообщение, включив в него работоспособный пример. Вы имеете в виду, что выполнение соединения дважды с одним и тем же LazyFrame, как ожидается, даст разные индексы, приводящие к вышеупомянутым расхождениям?
Я все еще пытаюсь понять, что делает полный код, но: если вы запустите пример несколько раз, result_direct_df
изменится. (вероятно, из-за случайного перетасовки .unique
порядка labels
), поэтому я не думаю, что в любом случае вы получите правильный результат.
@jqurious У меня есть расстояния между точками, помеченными буквами, и я сохраняю только уникальные пары col_1 и col_2 (игнорируя порядок). Затем я создаю метки с уникальными индексами для каждой буквы. При выполнении двух объединений для получения index_1 и index_2 для каждой записи в столбцах col_1 и col_2 (соответствующий индекс для каждой буквы) я получаю разные индексы для одной и той же буквы в index_1 и index_2. maintain_order=True
в обоих уникальных вызовах исправлена проблема с LazyFrame (которой нет в реализации DataFrame)
Вам нужен не только maintain_order=True
, но также keep = "first"
или keep = "last"
в каждом вызове .unique()
. В противном случае план запроса будет иметь произвольные назначения индексов. В частности, это присвоение может отличаться в последующих соединениях с labels
.
Причина, по которой вы получаете разные ответы, заключается в том, что unique
по умолчанию не сохраняет порядок. Поскольку индекс ваших меток будет отличаться в зависимости от порядка, это будет означать, что вы получите разные результаты. Единственное, что вам нужно сделать, чтобы это исправить, это:
labels = (
pl.concat([
pairwise_distances.select(pl.col("col_1").alias("label")),
pairwise_distances.select(pl.col("col_2").alias("label"))
])
.sort('label')
## Add maintain_order in this unique
.unique(keep = "first", maintain_order=True)
.with_row_count(name = "index")
)
map_elements
профилактикаВы можете выразить (я создаю это слово, если это еще не так) свой pairwise_distances
как
(
pairwise_distances
.filter(pl.col('col_1')<=pl.col('col_2'))
.unique(['col_1','col_2'])
.collect()
)
Предполагается, что у вас есть, например, A, B для каждого B, A. Если вы беспокоитесь, что в реальных данных у вас может быть B, A, но не A, B, тогда вы можете сделать
(
pairwise_distances
.select(
pl.min_horizontal('col_1','col_2').alias('col_1'),
pl.max_horizontal('col_1','col_2').alias('col_2'),
'col_3')
.unique(['col_1','col_2'])
)
Вам нужно будет предоставить некоторые образцы данных и сделать ваш пример работоспособным, чтобы мы могли увидеть различия.
.unique()
даст вам «случайный» порядок (если вы не используетеmaintain_order=True
) - так что кажется, что ваш код будет давать разные результаты каждый раз, когда он вызывается.