首页 > 解决方案 > 即使索引值恰好一致,也可以跨 pandas MultiIndex 级别广播

问题描述

这个让我难住了。我有两个pd.Series st如下:

Common  Level s
Foo     a          1
        b          2
Name: s, dtype: int64
Common  Level t
Foo     A          10
        B          20
Name: t, dtype: int64

pandas让我添加这些和跨通用级别的广播'Common'

输入:

s + t

输出:

Common  Level s  Level t
Foo     a        A          11
                 B          21
        b        A          12
                 B          22
dtype: int64

现在考虑另一个pd.Series u索引标签恰好与s

Common  Level u
Foo     a          100
        b          200
Name: u, dtype: int64

换句话说,我们有(s.index.values == u.index.values).all()回报True。正因为如此,pandas不再广播

输入:

s + u

输出:

Common  Level s
Foo     a          101
        b          202
dtype: int64

即使s.index.namesu.index.names不同意。

最后,如果更改了顺序但没有更改标签,例如 for v

Common  Level v
Foo     b          1000
        a          2000
Name: v, dtype: int64

因此,s.index.values如果v.index.values不完全同意,就会发生广播。

输入:

s + v

输出:

Common  Level s  Level v
Foo     a        b          1001
                 a          2001
        b        b          1002
                 a          2002
dtype: int64

我的问题:我怎样才能添加s这样upandas仍然广播?(对于我的特定应用程序,我实际上对 elementwise-and 感兴趣,而s & u不是 sum s + u。)


代码

s = pd.Series([1, 2],
              index=pd.MultiIndex.from_tuples(
                  [('Foo', 'a'), ('Foo', 'b')],
                  names=['Common', 'Level s']), name='s')

t = pd.Series([10, 20],
              index=pd.MultiIndex.from_tuples(
                  [('Foo', 'A'), ('Foo', 'B')],
                  names=['Common', 'Level t']), name='t')

u = pd.Series([100, 200],
              index=pd.MultiIndex.from_tuples(
                  [('Foo', 'a'), ('Foo', 'b')],
                  names=['Common', 'Level u']), name='u')

v = pd.Series([1000, 2000],
              index=pd.MultiIndex.from_tuples(
                  [('Foo', 'b'), ('Foo', 'a')],
                  names=['Common', 'Level v']), name='v')

标签: pandasmulti-indexarray-broadcasting

解决方案


为广播标准化 Series 和 DataFrames 的索引所调用的方法是align. 这个方法在内部被调用来获取新的索引,我们可以看到:

left, right = s.align(t, join='outer', level=None, copy=False)

# left
Common  Level s  Level t
Foo     a        A          1
                 B          1
        b        A          2
                 B          2
Name: s, dtype: int64
# right
Common  Level s  Level t
Foo     a        A          10
                 B          20
        b        A          10
                 B          20
Name: t, dtype: int64

请注意,当索引相等时,此调用将产生“非广播”值,因为外部连接产生单个级别:

left, right = s.align(u, join='outer', level=None, copy=False)

# left
Common  Level s
Foo     a          1
        b          2
Name: s, dtype: int64

# right
Common  Level u
Foo     a          100
        b          200
Name: u, dtype: int64

如果我们想强制生成级别,我们可以_align_series在索引不相等时使用 for 的分支:

join_index, lidx, ridx = self.index.join(
    other.index, how=join, level=level, return_indexers=True
) 
left = self._reindex_indexer(join_index, lidx, copy)
right = other._reindex_indexer(join_index, ridx, copy)

我们可以使用Index.joinand_reindex_indexer创建对齐的系列:

join_index, lidx, ridx = s.index.join(
    u.index, how='outer', level=None, return_indexers=True
)
left = s._reindex_indexer(join_index, lidx, copy=False)
right = u._reindex_indexer(join_index, ridx, copy=False)

*注意:我们使用的是私有方法,因为公共 API 中没有等效的重新索引器。

现在我们已经对齐了 Series,我们可以这样做:

left + right

要获得结果:

Common  Level s  Level u
Foo     a        a          101
                 b          201
        b        a          102
                 b          202
dtype: int64

如果我们想避免使用私有方法,我们还iloc可以使用索引位置选择值,然后set_axis用带有适当标签的连接索引覆盖:

join_index, lidx, ridx = s.index.join(
    u.index, how='outer', level=None, return_indexers=True
)
left = s.iloc[lidx].set_axis(join_index)
right = u.iloc[ridx].set_axis(join_index)
left + right
Common  Level s  Level u
Foo     a        a          101
                 b          201
        b        a          102
                 b          202
dtype: int64

推荐阅读