2026, Jan 09 09:01

Циклично повторяем короткий pandas DataFrame через merge

Покажем, как циклично повторить короткий pandas DataFrame до длины длинного: создаем ключ по модулю через numpy.arange и объединяем merge. Пример кода и вывод.

Циклично повторить более короткий pandas DataFrame до длины более длинного

Иногда нужно совместить два DataFrame разной длины и заставить короткий повторяться — по кругу дублировать его строки, пока он не сравняется по длине с длинным. Со списками Python это элементарно через itertools.cycle, но для DataFrame понадобится иной прием.

Простая мысленная модель со списками

from itertools import cycle
a = range(7)
b = range(43)
paired = zip(cycle(a), b)

Здесь a начинается заново при достижении конца, и паруется с полной длиной b. Попытка напрямую применить эту идею в pandas.concat не сработает, потому что конкатенация DataFrame ожидает реальные, выровненные объекты, а не бесконечный итератор строк.

Наивная попытка в pandas

import pandas as pd
from itertools import cycle
left_df = pd.DataFrame(...)  # длина 7
right_df = pd.DataFrame(...)  # длина 43
bad_combo = pd.concat([cycle(left_df), right_df], axis=1)

Это падает, потому что cycle(left_df) не возвращает конкретный, индексируемый DataFrame. Вместо повторяющегося блока для выравнивания по столбцам получается бесконечный генератор строк, который concat целиком потребить не может.

Что здесь на самом деле происходит

Чтобы воспроизвести поведение cycle для DataFrame, нужен детерминированный способ сопоставить каждой строке длинного DataFrame строку короткого, начиная сначала, когда варианты заканчиваются. Аккуратный путь — вычислить ключ на строку с помощью numpy.arange и оператора взятия по модулю. Модуль создает повторяющийся рисунок целых чисел — по сути индексы перезапуска, аналогичные itertools.cycle. Имея общий ключ, merge выстраивает таблицы друг напротив друга.

Решение: сформируйте повторяющийся ключ для join и выполните merge

import numpy as np
import pandas as pd
# Пример входных данных
left_df = pd.DataFrame({
    'col1': list('ABCDEFG'),
    'col2': ['X'] * 7
})
right_df = pd.DataFrame({
    'col3': list('abc'),
    'col4': ['Y'] * 3
})
# Выравнивание как в cycle с помощью ключей на основе модуля
result_df = (
    left_df.merge(
        right_df,
        left_on=np.arange(len(left_df)) % len(right_df),
        right_on=np.arange(len(right_df)) % len(left_df)
    )
    .drop(columns=['key_0'])
)

В результате получится DataFrame длины более длинного входа с повторяющимся, циклическим соответствием из короткого — перезапуск будет происходить точно так же, как в itertools.cycle. Вот итоговый вывод:

  col1 col2 col3 col4
0    A    X    a    Y
1    B    X    b    Y
2    C    X    c    Y
3    D    X    a    Y
4    E    X    b    Y
5    F    X    c    Y
6    G    X    a    Y

Если хотите увидеть выравнивающий ключ, управляющий поведением, просто не удаляйте его:

   key_0 col1 col2 col3 col4
0      0    A    X    a    Y
1      1    B    X    b    Y
2      2    C    X    c    Y
3      0    D    X    a    Y
4      1    E    X    b    Y
5      2    F    X    c    Y
6      0    G    X    a    Y

Почему это важно

Выравнивание данных — центральная идея pandas. Когда длины различаются, попытки «в лоб» повторять строки приводят к хрупким или неработоспособным конструкциям. Ключ на основе модуля дает точный, векторизованный способ выразить «цикл для этой более короткой таблицы» и опирается на merge, созданный для реляционного выравнивания. Итог предсказуем и хорошо масштабируется.

Выводы

Когда требуется циклично повторять один DataFrame относительно другого, сгенерируйте повторяющийся ключ через numpy.arange и оператор модуля и выполните merge по этому ключу. Такой подход повторяет поведение itertools.cycle, остается идиоматичным для pandas, избегает хрупких обходных путей и делает логику явной и поддерживаемой.