搜索和比较数据帧之间的数据

时间:2017-12-12 01:21:36

标签: pandas dataframe

我有关于数据框合并的问题。 我有两个数据框如下,

df1:
ID name-group status
1  bob,david  good
2  CC,robben  good
3  jack       bad

df2:
ID  leader   location
2   robben   JAPAN
3   jack     USA
4   bob      UK

我希望得到一个结果作为流程。

dft
ID  name-group Leader location
1   bob,david   
2   CC,robben  Robben JAPAN
3   jack       Jack   USA

时合并

[leader] in df2 **IN** [name-group] of df1
&
[ID] of df2 **=** [ID] of df1

我试过循环,但它的时间成本非常高。 关于这个问题的任何想法?

由于

2 个答案:

答案 0 :(得分:1)

查看可运行代码的帖子结尾。建议的解决方案在函数using_tidy

这里的主要问题是在name-group中有多个名称,分开 通过逗号,使搜索成员资格变得困难。相反,如果df1各有一个 name-group的成员在自己的行中,然后测试成员资格 简单。也就是说,假设df1看起来像这样:

   ID  name-group status
0   1  bob        good
0   1  david      good
1   2  CC         good
1   2  robben     good
2   3  jack       bad

然后你可以简单地在df1上合并df2ID并测试leader 等于 name-group ...差不多(看看为什么“差不多”在下面)。

df1置于整洁的格式(PDF) 是下面解决方案的主要思想。它之所以能提高性能 是因为测试两列之间的相等性要快得多 测试一列字符串是否是另一列字符串的子串,或者 是包含字符串列表的列的成员。

我上面说“差不多”的原因是因为还有另一个困难 - 在df1上合并df2ID后,某些行是无领的,例如bob,david行:

ID  name-group Leader location
1   bob,david   

由于我们只想保留这些行,并且我们不想测试条件#1在这种情况下是否成立,我们需要区别对待这些行 - 不要扩展它们。 我们可以通过将无领导行与具有潜在领导者的行分开来处理这个问题(见下文)。

通过在df1上合并df2ID,可以轻松实施ID匹配的第二个条件:

dft = pd.merge(df1, df2, on='ID', how='left')

第一个标准是dft['leader']位于dft['name-group']。 该标准可以表示为

In [293]: dft.apply(lambda x: pd.isnull(x['leader']) or (x['leader'] in x['name-group'].split(',')), axis=1)
Out[293]: 
0    True
1    True
2    True
dtype: bool

但是使用dft.apply(..., axis=1)会为lambda函数调用一次 行。如果dft中有很多行,这可能会非常慢。

如果dft中有很多行,我们可以先将dft转换为更好 整齐的格式(PDF) - 放置每个 dft['name-group']中的成员在其自己的行上。但首先,让我们将dft分成2 sub-DataFrames,有领导者的那些,以及没有领导者的那些:

has_leader = pd.notnull(dft['leader'])
leaderless, leaders = dft.loc[~has_leader, :], dft.loc[has_leader, :]

现在将leaders置于整齐的格式(每行一个成员):

member = leaders['name-group'].str.split(',', expand=True)
member = member.stack()
member.index = member.index.droplevel(1)
member.name = 'member'
leaders = pd.concat([member, leaders], axis=1)

所有这些工作的回报是标准#1现在可以通过快速计算来表达:

# this enforces criteria #1 (leader of df2 is in name-group of df1)
mask = (leaders['leader'] == leaders['member']) 
leaders = leaders.loc[mask, :]
leaders = leaders.drop('member', axis=1)

,所需的结果是:

dft = pd.concat([leaderless, leaders], axis=0)

我们必须做一些工作才能使df1变成整洁的格式。我们需要基准测试 通过能够更快地计算标准#1来确定做额外工作的成本是否会得到回报。

以下是df1df2使用1000行较大数据帧的基准:

In [356]: %timeit using_tidy(df1, df2)
100 loops, best of 3: 17.8 ms per loop

In [357]: %timeit using_apply(df1, df2)
10 loops, best of 3: 98.2 ms per loop

using_tidy超过using_apply的速度优势随着数量的增加而增加 pd.merge(df1, df2, on='ID', how='left')中的行数增加。

以下是基准测试的设置:

import string
import numpy as np
import pandas as pd


df1 = pd.DataFrame({'name-group':['bob,david', 'CC,robben', 'jack'],
                    'status':['good','good','bad'],
                    'ID':[1,2,3]})
df2 = pd.DataFrame({'leader':['robben','jack','bob'],
                    'location':['JAPAN','USA','UK'],
                    'ID':[2,3,4]})

def using_apply(df1, df2):
    dft = pd.merge(df1, df2, on='ID', how='left')
    mask = dft.apply(lambda x: pd.isnull(x['leader']) or (x['leader'] in x['name-group'].split(',')), axis=1)
    return dft.loc[mask, :]

def using_tidy(df1, df2):
    # this enforces criteria #2 (the IDs are the same)
    dft = pd.merge(df1, df2, on='ID', how='left')

    # split dft into 2 sub-DataFrames, based on rows which have a leader and those which do not.
    has_leader = pd.notnull(dft['leader'])
    leaderless, leaders = dft.loc[~has_leader, :], dft.loc[has_leader, :]

    # expand leaders so each member in name-group has its own row
    member = leaders['name-group'].str.split(',', expand=True)
    member = member.stack()
    member.index = member.index.droplevel(1)
    member.name = 'member'
    leaders = pd.concat([member, leaders], axis=1)

    # this enforces criteria #1 (leader of df2 is in name-group of df1)
    mask = (leaders['leader'] == leaders['member']) 
    leaders = leaders.loc[mask, :]
    leaders = leaders.drop('member', axis=1)

    dft = pd.concat([leaderless, leaders], axis=0)
    return dft

def make_random_str_array(letters=string.ascii_uppercase, strlen=10, size=100):
    return (np.random.choice(list(letters), size*strlen)
            .view('|U{}'.format(strlen)))

def make_dfs(N=1000):
    names = make_random_str_array(strlen=4, size=10)
    df1 = pd.DataFrame({
        'name-group':[','.join(np.random.choice(names, size=np.random.randint(1,10), replace=False)) for i in range(N)],
        'status':np.random.choice(['good','bad'], size=N),
        'ID':np.random.randint(4, size=N)})
    df2 = pd.DataFrame({
        'leader':np.random.choice(names, size=N),
        'location':np.random.randint(10, size=N),
        'ID':np.random.randint(4, size=N)})

    return df1, df2

df1, df2 = make_dfs()

答案 1 :(得分:0)

为什么不使用

Dft = pd.merge(df1,df2,how=‘left’,left_on = [‘ID’],right_on =[‘ID’])