web-dev-qa-db-fra.com

Meilleure façon de rejoindre/fusionner par rangée de pandas

J'utilise fréquemment des pandas pour fusionner (joindre) en utilisant une condition de portée.

Par exemple, s'il y a 2 cadres de données:

A(A_id, A_value)

B(B_id, B_low, B_high, B_name)

qui sont gros et approximativement de la même taille (disons 2M enregistre chacun).

Je voudrais faire une jointure interne entre A et B, ainsi A_value serait entre B_low et B_high.

Utiliser la syntaxe SQL qui serait:

SELECT *
FROM A,B
WHERE A_value between B_low and B_high

et ce serait vraiment facile, court et efficace.

Pendant ce temps, dans les pandas, le seul moyen (sans utiliser les boucles que j'ai trouvées), est de créer une colonne factice dans les deux tables, de la joindre (équivalent à une jointure croisée), puis de filtrer les lignes inutiles. Cela semble lourd et complexe:

A['dummy'] = 1
B['dummy'] = 1
Temp = pd.merge(A,B,on='dummy')
Result = Temp[Temp.A_value.between(Temp.B_low,Temp.B_high)]

Une autre solution que j’avais est d’appliquer sur chaque valeur A une fonction de recherche sur B à l’aide de B[(x>=B.B_low) & (x<=B.B_high)] mask, mais cela semble également inefficace et peut nécessiter l’optimisation de l’index.

Existe-t-il un moyen plus élégant et/ou plus efficace d'effectuer cette action?

16
Dimgold

Installer
Considérons les cadres de données A et B

A = pd.DataFrame(dict(
        A_id=range(10),
        A_value=range(5, 105, 10)
    ))
B = pd.DataFrame(dict(
        B_id=range(5),
        B_low=[0, 30, 30, 46, 84],
        B_high=[10, 40, 50, 54, 84]
    ))

A

   A_id  A_value
0     0        5
1     1       15
2     2       25
3     3       35
4     4       45
5     5       55
6     6       65
7     7       75
8     8       85
9     9       95

B

   B_high  B_id  B_low
0      10     0      0
1      40     1     30
2      50     2     30
3      54     3     46
4      84     4     84

numpy
Le "plus facile" moyen est d'utiliser la diffusion numpy.
Nous recherchons chaque instance de A_value supérieure ou égale à B_low alors que, simultanément, A_value est inférieur ou égal à B_high.

a = A.A_value.values
bh = B.B_high.values
bl = B.B_low.values

i, j = np.where((a[:, None] >= bl) & (a[:, None] <= bh))

pd.DataFrame(
    np.column_stack([A.values[i], B.values[j]]),
    columns=A.columns.append(B.columns)
)

   A_id  A_value  B_high  B_id  B_low
0     0        5      10     0      0
1     3       35      40     1     30
2     3       35      50     2     30
3     4       45      50     2     30

Pour répondre aux commentaires et donner un résultat similaire à une jointure gauche, j'ai ajouté la partie de A qui ne correspond pas.

pd.DataFrame(
    np.column_stack([A.values[i], B.values[j]]),
    columns=A.columns.append(B.columns)
).append(
    A[~np.in1d(np.arange(len(A)), np.unique(i))],
    ignore_index=True, sort=False
)

    A_id  A_value  B_id  B_low  B_high
0      0        5   0.0    0.0    10.0
1      3       35   1.0   30.0    40.0
2      3       35   2.0   30.0    50.0
3      4       45   2.0   30.0    50.0
4      1       15   NaN    NaN     NaN
5      2       25   NaN    NaN     NaN
6      5       55   NaN    NaN     NaN
7      6       65   NaN    NaN     NaN
8      7       75   NaN    NaN     NaN
9      8       85   NaN    NaN     NaN
10     9       95   NaN    NaN     NaN
16
piRSquared

Pas sûr que ce soit plus efficace, cependant vous pouvez utiliser SQL directement (à partir du module sqlite3 par exemple) avec des pandas (inspirés de cette question ) comme:

conn = sqlite3.connect(":memory:") 
df2 = pd.DataFrame(np.random.randn(10, 5), columns=["col1", "col2", "col3", "col4", "col5"])
df1 = pd.DataFrame(np.random.randn(10, 5), columns=["col1", "col2", "col3", "col4", "col5"])
df1.to_sql("df1", conn, index=False)
df2.to_sql("df2", conn, index=False)
qry = "SELECT * FROM df1, df2 WHERE df1.col1 > 0 and df1.col1<0.5"
tt = pd.read_sql_query(qry,conn)

Vous pouvez adapter la requête selon vos besoins dans votre application.

2
Adonis

Je ne sais pas à quel point c'est efficace, mais quelqu'un a écrit un wrapper qui vous permet d'utiliser la syntaxe SQL avec des objets pandas. Cela s'appelle pandasql . La documentation indique explicitement que les jointures sont prises en charge. Cela pourrait être au moins plus facile à lire puisque la syntaxe SQL est très lisible.

2
baloo

Considérez que votre dataframe est

A = pd.DataFrame([[0,2],[1,3],[2,4],[3,5],[4,6]],columns=['A_id', 'A_value'])

et B dataframe est 

B = pd.DataFrame([[0,1,2,'a'],[1,4,9,'b'],[2,2,5,'c'],[3,6,7,'d'],[4,8,9,'e']],columns=['B_id', 'B_low', 'B_high', 'B_name'])

en utilisant ceci ci-dessous, vous obtiendrez la sortie souhaitée

A = A[(A['A_value']>=B['B_low'])&(A['A_value']<=B['B_high'])]
0
Akshay Kandul

prenons un exemple simple:

df=pd.DataFrame([2,3,4,5,6],columns=['A'])

résultats

    A
0   2
1   3
2   4
3   5
4   6

permet maintenant de définir un deuxième cadre de données

df2=pd.DataFrame([1,6,2,3,5],columns=['B_low'])
df2['B_high']=[2,8,4,6,6]

résulte en 

    B_low   B_high
0   1       2
1   6       8
2   2       4
3   3       6
4   5       6

et c'est parti; et nous voulons que la sortie soit index 3 et une valeur 5 

df.where(df['A']>=df2['B_low']).where(df['A']<df2['B_high']).dropna()

résulte en 

    A
3   5.0
0
suvy