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?
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
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.
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.
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'])]
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