【问题标题】:Concatenate Series in pandas throwing away overlapping indexes熊猫中的串联系列丢弃重叠的索引
【发布时间】:2019-09-19 07:01:27
【问题描述】:

假设我有以下三个索引重叠的系列

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 可能有许多大长度系列时,是否有一种快速有效的方法来执行此操作。

编辑

我正在寻找一种高效的方法来做到这一点,因为实际上系列的长度和数量都很大,分别约为 100k 和 10k。

【问题讨论】:

    标签: pandas numpy dataframe series


    【解决方案1】:

    想法是使用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])
    

    【讨论】:

    • 这看起来很有效。当它返回一个 numpy 数组时,是否可以跟踪每个数据点来自哪个索引?
    【解决方案2】:

    我可以建议的最快如下:

    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)
    

    对于比这更大的任何事情,尽管您可能需要考虑使用不同的方法,例如 map-reduce。

    【讨论】:

      【解决方案3】:

      这应该可以解决问题。让我知道:

      listc = s3.append(s2).append(s1).reset_index().drop_duplicates(subset='index', keep='first').set_index('index').sort_index()
      

      【讨论】:

      • 当系列数量很大时,我认为这不是一个好的解决方案
      【解决方案4】:

      这是我的看法:

      # 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
      

      【讨论】:

      • 当系列数量很大时,这种方法非常慢
      【解决方案5】:

      使用functools.reducepd.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
      

      【讨论】:

      • 这个方法在series数量很大的时候比较慢
      猜你喜欢
      • 2020-05-01
      • 2021-12-07
      • 1970-01-01
      • 2022-11-08
      • 2016-02-06
      • 2018-12-15
      • 2014-10-03
      • 2013-08-07
      • 2018-03-25
      相关资源
      最近更新 更多