在 pandas MultiIndex 级别广播,即使索引值恰好一致

Broadcast across pandas MultiIndex level even if index values happen to agree

这个让我难住了。我有两个 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,其中索引 labels 恰好与 s

的索引一致
Common  Level u
Foo     a          100
        b          200
Name: u, dtype: int64

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

输入:

s + u

输出:

Common  Level s
Foo     a          101
        b          202
dtype: int64

尽管 s.index.namesu.index.names 不同意。

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

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

所以 s.index.valuesv.index.values 不完全一致,然后广播发生。

输入:

s + v

输出:

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

我的问题:如何添加 su 以便 pandas 仍然广播? (对于我的特定应用程序,我实际上对 elementwise-and s & u 感兴趣,而不是总和 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')

为广播规范化Series和DataFrame的索引调用的方法是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 中的分支来处理索引不相等的情况:

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.join_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)

*注意:我们正在使用私有方法,因为 public API.

中没有等效的重新索引器

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

left + right

获取结果:

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

如果我们想避免使用私有方法,我们也可以iloc to select values using index locations then 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