Ряд конкатенаций в пандах, выбрасывающих перекрывающиеся индексы - PullRequest
3 голосов
/ 30 апреля 2019

Предположим, у меня есть следующие три серии с перекрывающимися индексами

s1 = pd.Series(data=np.arange(5))
s2 = pd.Series(data=np.arange(5),index=np.arange(2,7))
s3 = pd.Series(data=np.arange(5),index=np.arange(5,10))

Я хочу объединить их в одну серию;тем не менее, я хочу, чтобы значения данных в пересекающихся индексах были взяты из ряда, который имеет «последний» индекс.

Следовательно, в игрушечном случае результат будет:

0    0
1    1
2    0
3    1
4    2
5    0
6    1
7    2
8    3
9    4
dtype: int32

Это также можно рассматривать как разрезание каждой серии в точке, в которой она перекрывается со следующей серией, а затем просто конкатенация.Существует ли быстрый и эффективный способ сделать это в pandas, когда может быть много серий большой длины.

РЕДАКТИРОВАТЬ

Я ищу эффективный способ сделать это, поскольку в действительности длина и количество серий велики, около 100 тыс. и 10 тыс. соответственно.

Ответы [ 5 ]

2 голосов
/ 01 мая 2019

Идея заключается в использовании concatenate для сглаживания индексов и значений Series и фильтрации по Series.duplicated с инвертированной маской по ~:

def new1(series):
    b = [x.index for x in series]
    v = np.concatenate(series)
    i = np.concatenate(b)

    mask = ~pd.Series(i).duplicated(keep='last')
    return pd.Series(v[mask], index=i[mask])
1 голос
/ 01 мая 2019

Самое быстрое, что я могу предложить, это следующее:

series = [s1, s2, s3]
s = pd.concat(series)
your_series = s[(~s.index[::-1].duplicated())[::-1]]

Вы можете сравнить время:

import functools

def method1(series):
    s = pd.concat(series)
    return s[(~s.index[::-1].duplicated())[::-1]]

def method2(series):
    s1,s2,s3 = series
    return functools.reduce(pd.Series.combine_first, [s3,s2,s1])

def method3(series):
    s1,s2,s3 = series
    listc = s3.append(s2).append(s1).reset_index().drop_duplicates(
        subset='index', keep='first').set_index('index').sort_index()
    return listc

def method4(series):
    return pd.DataFrame(series).ffill().tail(1).T

Результаты:

>>> %timeit method1(series)
... 643 µs ± 25.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

>>> %timeit method2(series)
... 1.15 ms ± 26.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

>>> %timeit method3(series)
... 3.09 ms ± 262 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

>>> %timeit method4(series)
... 1.07 ms ± 16.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

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


РЕДАКТИРОВАТЬ:

Небольшой тест для гораздо более крупных серий:

series = [pd.Series(data=np.arange(100000), index=np.arange(i*5000,100000+i*5000)) for i in range(100)]

Результат:

>>> %timeit method1(series)
... 583 ms ± 18.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit method2(series)
... 4.5 s ± 25.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Однако, если число серий меньше, а размер каждой серии больше, тогда разница намного меньше:

series = [pd.Series(data=np.arange(1000000), index=np.arange(i*5000,1000000+i*5000)) for i in range(10)]

Результат:

>>> %timeit method1(series)
... 679 ms ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit method2(series)
... 1.39 s ± 26.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

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

0 голосов
/ 30 апреля 2019

Это должно сработать. Дайте мне знать:

listc = s3.append(s2).append(s1).reset_index().drop_duplicates(subset='index', keep='first').set_index('index').sort_index()
0 голосов
/ 30 апреля 2019

С functools.reduce и pd.Series.combine_first

import functools

functools.reduce(pd.Series.combine_first,[s3,s2,s1])
Out[794]: 
0    0.0
1    1.0
2    0.0
3    1.0
4    2.0
5    0.0
6    1.0
7    2.0
8    3.0
9    4.0
dtype: float64
0 голосов
/ 30 апреля 2019

Вот мой дубль:

# create a dataframe with all series
df = pd.DataFrame({'s1':s1, 's2':s2, 's3': s3})

# ffill for the latest index:
df.ffill(1)

Вывод (ожидается, что последний столбец):

      s1    s2    s3
--  ----  ----  ----
 0     0     0     0
 1     1     1     1
 2     2     0     0
 3     3     1     1
 4     4     2     2
 5   nan     3     0
 6   nan     4     1
 7   nan   nan     2
 8   nan   nan     3
 9   nan   nan     4

Один вкладыш:

 df = pd.DataFrame([s1,s2,s3]).ffill().tail(1).T

Вывод:

      2
--  ---
 0    0
 1    1
 2    0
 3    1
 4    2
 5    0
 6    1
 7    2
 8    3
 9    4
...