Почему сбор LazyFrame перед объединением в Polars решает мою проблему с несоответствиями индексов?

Вот пример, который можно запустить и который демонстрирует проблему. Начальный 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 перед выполнением соединений, индексы верны.

Вот соответствующие фрагменты кода:

  1. Создание меток LazyFrame:
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")
)

  1. Присоединение без сбора (это дает неверные индексы):
# 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()

  1. Присоединение после сбора меток (это дает правильные значения index_1 и index_2):
# 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?

Будем очень признательны за любую информацию о том, почему это происходит и как это решить!

Вам нужно будет предоставить некоторые образцы данных и сделать ваш пример работоспособным, чтобы мы могли увидеть различия. .unique() даст вам «случайный» порядок (если вы не используете maintain_order=True) - так что кажется, что ваш код будет давать разные результаты каждый раз, когда он вызывается.

jqurious 24.06.2024 13:35

@jqurious Я отредактировал исходное сообщение, включив в него работоспособный пример. Вы имеете в виду, что выполнение соединения дважды с одним и тем же LazyFrame, как ожидается, даст разные индексы, приводящие к вышеупомянутым расхождениям?

xaostheory 24.06.2024 13:57

Я все еще пытаюсь понять, что делает полный код, но: если вы запустите пример несколько раз, result_direct_df изменится. (вероятно, из-за случайного перетасовки .unique порядка labels), поэтому я не думаю, что в любом случае вы получите правильный результат.

jqurious 24.06.2024 14:14

@jqurious У меня есть расстояния между точками, помеченными буквами, и я сохраняю только уникальные пары col_1 и col_2 (игнорируя порядок). Затем я создаю метки с уникальными индексами для каждой буквы. При выполнении двух объединений для получения index_1 и index_2 для каждой записи в столбцах col_1 и col_2 (соответствующий индекс для каждой буквы) я получаю разные индексы для одной и той же буквы в index_1 и index_2. maintain_order=True в обоих уникальных вызовах исправлена ​​проблема с LazyFrame (которой нет в реализации DataFrame)

xaostheory 24.06.2024 14:26

Вам нужен не только maintain_order=True, но также keep = "first" или keep = "last" в каждом вызове .unique(). В противном случае план запроса будет иметь произвольные назначения индексов. В частности, это присвоение может отличаться в последующих соединениях с labels.

Hericks 24.06.2024 17:07
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
5
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ответ на вопрос

Причина, по которой вы получаете разные ответы, заключается в том, что 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'])
)

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

Python: только 2 уникальных имени столбца в фрейме данных, всего 3105 столбцов. Как получить среднее значение строки, сгруппированной по уникальному имени столбца
Как я могу сравнить значение в одном столбце со всеми значениями, которые находятся ДО него в другом столбце, чтобы найти количество уникальных значений, которые меньше?
Присоединиться к фрейму данных с двойной записью
Получите минимум за счет итераций записи в фрейме данных pandas
Добавьте количество строк в виде списка в столбец, используя groupby
Сохраните данные на новой вкладке в файле .xlsx
Pandas groupby — группировать столбцы в список количества значений строк
Как удалить все строки, где хотя бы один столбец не имеет значения 1?
Как отсортировать фрейм данных (в базе R) по всем столбцам, не зная заранее все столбцы
Как я могу использовать два цикла for вместе (по одному на каждый год и по одному на каждый день)?