pandas multiIndex 对仅用于顶部和底部 n 行

pandas multiIndex pair only for top and bottom n rows

我有一个 pandas 数据框,如下所示

Company,year                                   
T123 Inc Ltd,1990
T124 PVT ltd,1991
T345 Ltd,1990
T789 Pvt.LTd,2001
ABC Limited,1992
ABCDE Ltd,1994
ABC Ltd,1997
ABFE,1987
Tesla ltd,1995
AMAZON Inc,2001
Apple ltd,2003

tf = pd.read_clipboard(sep=',')
tf['Company_copy'] = tf['Company']

我想将 tf['company'] 中的每个值与其当前位置上方 5 行和下方 5 行进行比较。

例如:我希望将 T123 Inc LtdT124,T345,T789,ABC,ACDE 进行比较。由于T123是第一行,上面没有什么可比较的,我们不生成任何比较对。

类似地,如果没有足够的(5 行)行进行比较,我们将其与现有的任何行进行比较。

所以,我在这里

的帮助下尝试了下面的方法
pd.MultiIndex.from_product([tf['Company'].astype(str),tf['Company_copy'].astype(str)]).to_series()

但它会在数据帧的所有行中生成 m*n 比较

这是因为,我们有百万条记录,无法使用 multiindex_from 产品来生成所有比较(这对我们没有用)。每行只需顶部和底部 5 个比较对就足够了。无论如何应用过滤器多索引来生成基于 5 行以上和以下的对?

我希望我的输出如下所示。我只显示一条记录 T123 Inc Ltd.

Company       Company     
  
T123 Inc Ltd  T124 PVT ltd    (T123 Inc Ltd, T124 PVT ltd)
              T345 Ltd            (T123 Inc Ltd, T345 Ltd)
              T789 Pvt.LTd    (T123 Inc Ltd, T789 Pvt.LTd)
              ABC Limited      (T123 Inc Ltd, ABC Limited)
              ABCDE Ltd          (T123 Inc Ltd, ABCDE Ltd)

Company 上使用 Series.rollingcenter=True 和 window 大小 11 (=5+1+5) 的可能解决方案,然后排除元组中间一行:

from itertools import chain, product

idx = pd.MultiIndex.from_tuples(chain(*(product([row], win)
                         for row, win in zip(tf['Company'], tf['Company'].rolling(11, min_periods=1, center=True))))).to_series()
        
idx = idx[idx.index.get_level_values(0) != idx.index.get_level_values(1)]

示例结果:

print(idx['T123 Inc Ltd'])
T124 PVT ltd    (T123 Inc Ltd, T124 PVT ltd)
T345 Ltd            (T123 Inc Ltd, T345 Ltd)
T789 Pvt.LTd    (T123 Inc Ltd, T789 Pvt.LTd)
ABC Limited      (T123 Inc Ltd, ABC Limited)
ABCDE Ltd          (T123 Inc Ltd, ABCDE Ltd)
dtype: object
print(idx['ABCDE Ltd'])
T123 Inc Ltd    (ABCDE Ltd, T123 Inc Ltd)
T124 PVT ltd    (ABCDE Ltd, T124 PVT ltd)
T345 Ltd            (ABCDE Ltd, T345 Ltd)
T789 Pvt.LTd    (ABCDE Ltd, T789 Pvt.LTd)
ABC Limited      (ABCDE Ltd, ABC Limited)
ABC Ltd              (ABCDE Ltd, ABC Ltd)
ABFE                    (ABCDE Ltd, ABFE)
Tesla ltd          (ABCDE Ltd, Tesla ltd)
AMAZON Inc        (ABCDE Ltd, AMAZON Inc)
Apple ltd          (ABCDE Ltd, Apple ltd)
dtype: object
print(idx['Apple ltd'])
ABCDE Ltd      (Apple ltd, ABCDE Ltd)
ABC Ltd          (Apple ltd, ABC Ltd)
ABFE                (Apple ltd, ABFE)
Tesla ltd      (Apple ltd, Tesla ltd)
AMAZON Inc    (Apple ltd, AMAZON Inc)
dtype: object

接下来五行:

有两种方法可以实现:

  • 使用带有正向索引的自定义索引器:
indexer = pd.api.indexers.FixedForwardWindowIndexer(window_size=6)
idx = pd.MultiIndex.from_tuples(chain(*(product([row], win[1:])
                         for row, win in zip(tf['Company'], 
                                             tf['Company'].rolling(indexer, min_periods=1))))).to_series()
  • 滚动前反转DataFrame(我在mozway的回答中学到了这个):
idx = pd.MultiIndex.from_tuples(chain(*(product([row], win[::-1])
                         for row, win in zip(tf['Company'][::-1], 
                                             tf['Company'][::-1]
                                             .rolling(5, min_periods=1, closed='left'))))).to_series()

示例:

print(idx['T123 Inc Ltd'])
T124 PVT ltd    (T123 Inc Ltd, T124 PVT ltd)
T345 Ltd            (T123 Inc Ltd, T345 Ltd)
T789 Pvt.LTd    (T123 Inc Ltd, T789 Pvt.LTd)
ABC Limited      (T123 Inc Ltd, ABC Limited)
ABCDE Ltd          (T123 Inc Ltd, ABCDE Ltd)
dtype: object

但是,我认为这里的自定义索引器是最干净的,因为它更清楚它的作用。

这取决于您真正想要比较的程度,这可能适合您也可能不适合您。

我会遍历数据框并创建新列。像...

for i in range(5):
    # df['company_value'] represents the column that contains representative company values
    df[f'compare below {i}'] = list(df.loc[[i+1:],'company_value']) + ([np.nan] * (i+1))

for i in range(5):
    # df['company_value'] represents the column that contains representative company values
    df[f'compare above {i}'] = ([np.nan] * (i+1)) + list(df.loc[[:-(i+1)],'company_value'])

这 2 个循环将为您提供 2x5 列,其中包含上下 5 家公司的值。您可以对它们执行和操作,然后删除列

语法肯定可以改进(也许不必使用列表),但这应该可以正常工作。