Как лучше всего объединить два кадра данных, один из которых имеет перекрывающиеся диапазоны?

Мои DataFrames:

import pandas as pd

df_1 = pd.DataFrame(
    {
        'a': [10, 12, 14, 20, 25, 30, 42, 50, 80]
    }
)

df_2 = pd.DataFrame(
    {
        'start': [9, 19],
        'end': [26, 50],
        'label': ['a', 'b']
    }
)

Ожидаемый результат: добавление столбца label к df_1:

a    label
10    a
12    a
14    a
20    a
25    a
20    b
25    b
30    b
42    b
50    b

df_2 определяет диапазоны меток. Например, первая строка df_2 начала диапазона — 9, а конец — 22. Теперь я хочу разрезать df_1 на основе начала и конца и присвоить эту метку срезу. Обратите внимание, что start является исключающим, а end включает. И диапазоны моих ярлыков перекрываются.

Это мои попытки. Первый работает, но я не уверен, что он лучший.

# attempt_1
dfc = pd.DataFrame([])
for idx, row in df_2.iterrows():
    start = row['start']
    end = row['end']
    label = row['label']
    df_slice = df_1.loc[df_1.a.between(start, end, inclusive='right')]
    df_slice['label'] = label
    dfc = pd.concat([df_slice, dfc], ignore_index=True)

## attempt 2
idx = pd.IntervalIndex.from_arrays(df_2['start'], df_2['end'], closed='both')
label = df_2.iloc[idx.get_indexer(df_1.a), 'label']
df_1['label'] = label.to_numpy()

Ваш подход — декартово/перекрестное соединение, которое подходит для небольших размеров, но дорого по мере роста размера данных. Использование iterrows тоже не улучшает ситуацию. Лучшим и более быстрым подходом является использование двоичного поиска. Ваши начальный и конечный столбцы монотонно отсортированы - двоичный поиск идеально подходит для этого и будет хорош как с точки зрения памяти, так и с точки зрения производительности.

sammywemmy 12.05.2024 00:16
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
1
90
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

быстрый вариант — Conditional_join из pyjanitor:

# pip install pyjanitor
import pandas as pd
import janitor
(df_1
.conditional_join(
    df_2, 
    ('a','start','>='),
    ('a','end','<='), 
    df_columns = 'a',
    right_columns='label')
)
    a label
0  10     a
1  12     a
2  14     a
3  20     a
4  20     b
5  25     a
6  25     b
7  30     b
8  42     b
9  50     b

Не могли бы вы объяснить, что вы подразумеваете под словом «быстро» в этом контексте?

Vitalizzare 11.05.2024 18:17

Обычно перед фильтрацией соединения диапазонов обрабатываются как декартовы соединения. Это может быть дорого, поскольку размер данных увеличивается. Conditional_join использует двоичный поиск, отсюда и слово быстрое (относительно декартова соединения).

sammywemmy 12.05.2024 00:03

Родной путь панд

Вы можете использовать понимание списка с помощью range и explode, чтобы создать фрейм данных для присоединения к df_1 для каждого элемента в начальном и конечном диапазонах df_2.

df_2_join = df_2.assign(
                 a=[range(s, e + 1) for s, e in zip(df_2["start"], df_2["end"])]
            ).explode("a")

df_1.merge(df_2_join).sort_values('start')

Выход:

    a  start  end label
0  10      9   26     a
1  12      9   26     a
2  14      9   26     a
3  20      9   26     a
5  25      9   26     a
4  20     19   50     b
6  25     19   50     b
7  30     19   50     b
8  42     19   50     b
9  50     19   50     b

Это форма декартова соединения, которая подходит для небольших размеров, но становится дороже по мере увеличения размера данных или увеличения диапазонов.

sammywemmy 12.05.2024 01:40

Я бы попытался объединить сгенерированные помеченные диапазоны, используя pandas.concat следующим образом:

template = df_1.set_index('a')
ranges = df_2.values

output = pd.concat(
    template.loc[start:end].assign(label=label) 
    for start, end, label in ranges
).reset_index()

Это близко к вашему решению с двумя основными отличиями:

  1. Все итерации сворачиваются во внутренний генератор.
  2. Мы используем df_1['a'] в качестве индекса, что подразумевается по его природе.
import pandas as pd
import numpy as np


df_1 = pd.DataFrame(
    {
        'a': [10, 12, 14, 20, 25, 30, 42, 50, 80]
    }
)

df_2 = pd.DataFrame(
    {
        'start': [9, 19],
        'end': [26, 50],
        'label': ['a', 'b']
    }
)

# Convert columns to numpy arrays
a_values = df_1['a'].to_numpy()
start_values = df_2['start'].to_numpy()
end_values = df_2['end'].to_numpy()
labels = df_2['label'].to_numpy()

# Create a boolean matrix where each element (i, j) indicates whether 
# a_values[i] falls within the j-th range
m1 = (a_values[:, None] > start_values)
m2 = (a_values[:, None] <= end_values)
mask = (m1 & m2) 

# Find the indices where mask is True
rows, cols = np.where(mask)

# Create the new DataFrame with all valid (value, label) pairs
expanded_df = pd.DataFrame({
    'a': a_values[rows],
    'label': labels[cols]
})

print(expanded_df)
'''
    a label
0  10     a
1  12     a
2  14     a
3  20     a
4  20     b
5  25     a
6  25     b
7  30     b
8  42     b
9  50     b
'''

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

Похожие вопросы

Почему randint распределен неравномерно?
Как использовать алгоритм агломеративной кластеризации из библиотеки Python scikit-learn с объявленным количеством объектов в кластере?
Неопределенный символ: _ZN15TracebackLoggerC1EPKc, версия libcudnn_ops_infer.so.8
Токенизаторы && Docker: не удалось создать колеса для токенизаторов, которые необходимы для установки проектов на основе pyproject.toml
Продукты Django не добавляются в корзину сразу
Мои потери при тестировании увеличиваются, но потери поездов для нейронной сети уменьшаются. Что я должен делать?
Установлен модуль Python `owiener`, но во время импорта продолжает появляться сообщение «Нет модуля с именем «owiener»»
Comfyui: python torch/изменение размера изображения – высота регулирует ширину, а ширина регулирует цвет?
Какие части несбалансированного конвейера обучения применяются к набору тестов?
Панды применяют стиль к одной строке на основе сравнения двух строк