在* multi-key * outer join

时间:2016-09-28 15:22:56

标签: python pandas

注意:下面的帖子是我earlier question的“多键”对应物。该早期问题的解决方案仅适用于连接在单个密钥上的情况,并且我不清楚如何将这些解决方案推广到下面给出的多密钥情况。因为,IME修改一个已经回答的问题的方式使得它收到的答案不合格,所以在SO中我不赞成,我将单独发布这个变体。我还向Meta SO发布了一个question,关于我是否应该删除此帖子,而是修改原始问题,但代价是使其当前答案无效。

以下是我正在使用的更大/更复杂的数据帧的小型/玩具版本:

>>> A
  key1 key2         u         v         w         x
0    a    G  0.757954  0.258917  0.404934  0.303313
1    b    H  0.583382  0.504687       NaN  0.618369
2    c    I       NaN  0.982785  0.902166       NaN
3    d    J  0.898838  0.472143       NaN  0.610887
4    e    K  0.966606  0.865310       NaN  0.548699
5    f    L       NaN  0.398824  0.668153       NaN

  key1 key2         y         z
0    a    G  0.867603       NaN
1    b    H       NaN  0.191067
2    c    I  0.238616  0.803179
3    d    G  0.080446       NaN
4    e    H  0.932834       NaN
5    f    I  0.706561  0.814467

(FWIW,在本文末尾,我提供了生成这些数据帧的代码。)

我想在key1key2列上生成这些数据框的外连接,这样外连接引起的新位置的默认值为0.0。 IOW,期望的结果看起来像这样

  key1 key2         u         v         w         x          y         z
0    a    G  0.757954  0.258917  0.404934  0.303313   0.867603       NaN
1    b    H  0.583382  0.504687       NaN  0.618369        NaN  0.191067
2    c    I       NaN  0.982785  0.902166       NaN   0.238616  0.803179
3    d    J  0.898838  0.472143       NaN  0.610887   0.000000  0.000000
4    e    K  0.966606   0.86531       NaN  0.548699   0.000000  0.000000
5    f    L       NaN  0.398824  0.668153       NaN   0.000000  0.000000
6    d    G  0.000000  0.000000  0.000000  0.000000   0.080446       NaN
7    e    H  0.000000  0.000000  0.000000  0.000000   0.932834       NaN
8    f    I  0.000000  0.000000  0.000000  0.000000   0.706561  0.814467

(请注意,此期望的输出包含一些NaN,即AB中已存在的NaN。)

merge方法让我分道扬but,但填充的默认值是NaN,而不是0.0:

>>> C = pandas.DataFrame.merge(A, B, how='outer', on=('key1', 'key2'))
>>> C
  key1 key2         u         v         w         x         y         z
0    a    G  0.757954  0.258917  0.404934  0.303313  0.867603       NaN
1    b    H  0.583382  0.504687       NaN  0.618369       NaN  0.191067
2    c    I       NaN  0.982785  0.902166       NaN  0.238616  0.803179
3    d    J  0.898838  0.472143       NaN  0.610887       NaN       NaN
4    e    K  0.966606  0.865310       NaN  0.548699       NaN       NaN
5    f    L       NaN  0.398824  0.668153       NaN       NaN       NaN
6    d    G       NaN       NaN       NaN       NaN  0.080446       NaN
7    e    H       NaN       NaN       NaN       NaN  0.932834       NaN
8    f    I       NaN       NaN       NaN       NaN  0.706561  0.814467

fillna方法无法产生所需的输出,因为它修改了一些应保持不变的位置:

>>> C.fillna(0.0)
  key1 key2         u         v         w         x         y         z
0    a    G  0.757954  0.258917  0.404934  0.303313  0.867603  0.000000
1    b    H  0.583382  0.504687  0.000000  0.618369  0.000000  0.191067
2    c    I  0.000000  0.982785  0.902166  0.000000  0.238616  0.803179
3    d    J  0.898838  0.472143  0.000000  0.610887  0.000000  0.000000
4    e    K  0.966606  0.865310  0.000000  0.548699  0.000000  0.000000
5    f    L  0.000000  0.398824  0.668153  0.000000  0.000000  0.000000
6    d    G  0.000000  0.000000  0.000000  0.000000  0.080446  0.000000
7    e    H  0.000000  0.000000  0.000000  0.000000  0.932834  0.000000
8    f    I  0.000000  0.000000  0.000000  0.000000  0.706561  0.814467

如何有效地实现所需的输出? (性能在这里很重要,因为我打算在比这里显示的更大的数据帧上执行此操作。)

重要事项:为了使示例保持最小,我将多列只包含两列;实际上,多键中的键数可能要大得多。建议的答案应该适用于由至少六列组成的多键。

FWIW,下面是生成示例数据框AB的代码。

from pandas import DataFrame
from collections import OrderedDict
from random import random, seed

def make_dataframe(rows, colnames):
    return DataFrame(OrderedDict([(n, [row[i] for row in rows])
                                 for i, n in enumerate(colnames)]))

maybe_nan = lambda: float('nan') if random() < 0.4 else random()

seed(0)

A = make_dataframe([['A', 'g', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['B', 'h', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['C', 'i', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['D', 'j', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['E', 'k', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()],
                    ['F', 'l', maybe_nan(), maybe_nan(), maybe_nan(), maybe_nan()]],
                   ('key1', 'key2', 'u', 'v', 'w', 'x'))

B = make_dataframe([['A', 'g', maybe_nan(), maybe_nan()],
                    ['B', 'h', maybe_nan(), maybe_nan()],
                    ['C', 'i', maybe_nan(), maybe_nan()],
                    ['D', 'g', maybe_nan(), maybe_nan()],
                    ['E', 'h', maybe_nan(), maybe_nan()],
                    ['F', 'i', maybe_nan(), maybe_nan()]],
                   ('key1', 'key2', 'y', 'z'))

1 个答案:

答案 0 :(得分:2)

keys设置为两个DF's的索引:

def index_set(frame, keys=['key1', 'key2']):
    frame.set_index(keys, inplace=True)
    return frame

子集DF's包含NaN值:

def nulls(frame):
    nulls_in_frame = frame[frame.isnull().any(axis=1)].reset_index()
    return nulls_in_frame

加入两个Df's。将已加入的DF与包含NaN的{​​{1}}的每个子集连接起来,并删除重复的值,将剩余的DF's左侧填充为0。

然后,使用NaN使用已加入combine_first的链接操作来修补值。

DF

最后,

def perform_join(fr_1, fr_2, keys=['key1', 'key2']):
    fr_1 = index_set(fr_1); frame_2 = index_set(fr_2)
    frame = fr_1.join(fr_2, how='outer').reset_index()
    cat_fr_1 = pd.concat([frame, nulls(fr_1)]).drop_duplicates(keys, keep=False).fillna(0)
    cat_fr_2 = pd.concat([frame, nulls(fr_2)]).drop_duplicates(keys, keep=False).fillna(0)
    fr_1_join = frame.combine_first(frame.fillna(cat_fr_1[fr_1.columns]))
    joined_frame = fr_1_join.combine_first(frame.fillna(cat_fr_2[fr_2.columns]))
    return joined_frame

Image