Il existe plusieurs façons de sélectionner des lignes dans un bloc de données pandas:
- Indexation booléenne (
df[df['col'] == value
])
- Indexation positionnelle (
df.iloc[...]
)
- Indexation des étiquettes (
df.xs(...)
)
df.query(...)
API
Ci-dessous, je vous montre des exemples de chacun, avec des conseils sur l'utilisation de certaines techniques. Supposons que notre critère est la colonne 'A'
=='foo'
(Remarque sur les performances: pour chaque type de base, nous pouvons simplifier les choses en utilisant l'API pandas ou nous pouvons nous aventurer en dehors de l'API, généralement dans numpy
, et accélérer les choses.)
Configuration
La première chose dont nous aurons besoin est d'identifier une condition qui servira de critère de sélection des lignes. Nous allons commencer par le cas de l'OP column_name == some_value
, et inclure quelques autres cas d'utilisation courants.
Emprunter à @unutbu:
import pandas as pd, numpy as np
df = pd.DataFrame({'A': 'foo bar foo bar foo bar foo foo'.split(),
'B': 'one one two three two two one three'.split(),
'C': np.arange(8), 'D': np.arange(8) * 2})
1. Indexation booléenne
... L'indexation booléenne nécessite de trouver la vraie valeur de la 'A'
colonne de chaque ligne égale à 'foo'
, puis d'utiliser ces valeurs de vérité pour identifier les lignes à conserver. En règle générale, nous nommeriez cette série, un tableau de valeurs de vérité, mask
. Nous le ferons ici aussi.
mask = df['A'] == 'foo'
Nous pouvons ensuite utiliser ce masque pour découper ou indexer la trame de données
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
C'est l'un des moyens les plus simples d'accomplir cette tâche et si les performances ou l'intuitivité ne sont pas un problème, cela devrait être la méthode que vous avez choisie. Cependant, si les performances sont un problème, vous souhaiterez peut-être envisager une autre façon de créer le fichier mask
.
2. Indexation positionnelle
L'indexation positionnelle ( df.iloc[...]
) a ses cas d'utilisation, mais ce n'est pas l'un d'entre eux. Afin d'identifier où couper, nous devons d'abord effectuer la même analyse booléenne que nous avons faite ci-dessus. Cela nous laisse effectuer une étape supplémentaire pour accomplir la même tâche.
mask = df['A'] == 'foo'
pos = np.flatnonzero(mask)
df.iloc[pos]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
3. Indexation des étiquettes
L' indexation des étiquettes peut être très pratique, mais dans ce cas, nous faisons encore plus de travail sans aucun avantage
df.set_index('A', append=True, drop=False).xs('foo', level=1)
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
4. df.query()
API
pd.DataFrame.query
est un moyen très élégant / intuitif d'effectuer cette tâche, mais est souvent plus lent. Cependant , si vous faites attention aux délais ci-dessous, pour les données volumineuses, la requête est très efficace. Plus que l'approche standard et d'une ampleur similaire à ma meilleure suggestion.
df.query('A == "foo"')
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Ma préférence est d’utiliser le Boolean
mask
De réelles améliorations peuvent être apportées en modifiant la façon dont nous créons notre Boolean
mask
.
mask
alternative 1
Utilisez le numpy
tableau sous-jacent et renoncez à la surcharge de création d'un autrepd.Series
mask = df['A'].values == 'foo'
Je montrerai des tests de temps plus complets à la fin, mais jetez un coup d'œil aux gains de performances que nous obtenons en utilisant l'exemple de trame de données. Tout d'abord, nous examinons la différence dans la création dumask
%timeit mask = df['A'].values == 'foo'
%timeit mask = df['A'] == 'foo'
5.84 µs ± 195 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
166 µs ± 4.45 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
L'évaluation mask
avec le numpy
tableau est environ 30 fois plus rapide. Cela est dû en partie au fait que l' numpy
évaluation est souvent plus rapide. Cela est également dû en partie au manque de surcharge nécessaire pour construire un index et un pd.Series
objet correspondant .
Ensuite, nous examinerons le moment du découpage avec l'un mask
par rapport à l'autre.
mask = df['A'].values == 'foo'
%timeit df[mask]
mask = df['A'] == 'foo'
%timeit df[mask]
219 µs ± 12.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
239 µs ± 7.03 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Les gains de performances ne sont pas aussi prononcés. Nous verrons si cela résiste à des tests plus robustes.
mask
alternative 2
Nous aurions pu également reconstruire la base de données. Il y a une grande mise en garde lors de la reconstruction d'une trame de données - vous devez en prendre soin dtypes
lorsque vous le faites!
Au lieu de cela, df[mask]
nous le ferons
pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
Si la trame de données est de type mixte, ce que notre exemple est, alors lorsque nous obtenons df.values
le tableau résultant est de dtype
object
et par conséquent, toutes les colonnes de la nouvelle trame de données seront de dtype
object
. Exigeant ainsi le astype(df.dtypes)
et tuant tous les gains de performance potentiels.
%timeit df[m]
%timeit pd.DataFrame(df.values[mask], df.index[mask], df.columns).astype(df.dtypes)
216 µs ± 10.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.43 ms ± 39.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Cependant, si le bloc de données n'est pas de type mixte, c'est un moyen très utile de le faire.
Donné
np.random.seed([3,1415])
d1 = pd.DataFrame(np.random.randint(10, size=(10, 5)), columns=list('ABCDE'))
d1
A B C D E
0 0 2 7 3 8
1 7 0 6 8 6
2 0 2 0 4 9
3 7 3 2 4 3
4 3 6 7 7 4
5 5 3 7 5 9
6 8 7 6 4 7
7 6 2 6 6 5
8 2 8 7 5 8
9 4 7 6 1 5
%%timeit
mask = d1['A'].values == 7
d1[mask]
179 µs ± 8.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Contre
%%timeit
mask = d1['A'].values == 7
pd.DataFrame(d1.values[mask], d1.index[mask], d1.columns)
87 µs ± 5.12 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Nous avons réduit le temps de moitié.
mask
l'alternative 3
@unutbu nous montre également comment utiliser pd.Series.isin
pour tenir compte de chaque élément d' df['A']
être dans un ensemble de valeurs. Cela équivaut à la même chose si notre ensemble de valeurs est un ensemble d'une valeur, à savoir 'foo'
. Mais il se généralise également pour inclure des ensembles de valeurs plus importants si nécessaire. Il s'avère que c'est encore assez rapide même s'il s'agit d'une solution plus générale. La seule vraie perte est dans l'intuitivité pour ceux qui ne connaissent pas le concept.
mask = df['A'].isin(['foo'])
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Cependant, comme auparavant, nous pouvons utiliser numpy
pour améliorer les performances tout en ne sacrifiant pratiquement rien. Nous utiliseronsnp.in1d
mask = np.in1d(df['A'].values, ['foo'])
df[mask]
A B C D
0 foo one 0 0
2 foo two 2 4
4 foo two 4 8
6 foo one 6 12
7 foo three 7 14
Calendrier
Je vais également inclure d'autres concepts mentionnés dans d'autres articles pour référence.
Code ci-dessous
Chaque colonne de ce tableau représente une trame de données de longueur différente sur laquelle nous testons chaque fonction. Chaque colonne montre le temps relatif pris, avec la fonction la plus rapide étant donné un indice de base de 1.0
.
res.div(res.min())
10 30 100 300 1000 3000 10000 30000
mask_standard 2.156872 1.850663 2.034149 2.166312 2.164541 3.090372 2.981326 3.131151
mask_standard_loc 1.879035 1.782366 1.988823 2.338112 2.361391 3.036131 2.998112 2.990103
mask_with_values 1.010166 1.000000 1.005113 1.026363 1.028698 1.293741 1.007824 1.016919
mask_with_values_loc 1.196843 1.300228 1.000000 1.000000 1.038989 1.219233 1.037020 1.000000
query 4.997304 4.765554 5.934096 4.500559 2.997924 2.397013 1.680447 1.398190
xs_label 4.124597 4.272363 5.596152 4.295331 4.676591 5.710680 6.032809 8.950255
mask_with_isin 1.674055 1.679935 1.847972 1.724183 1.345111 1.405231 1.253554 1.264760
mask_with_in1d 1.000000 1.083807 1.220493 1.101929 1.000000 1.000000 1.000000 1.144175
Vous remarquerez que les temps les plus rapides semblent être partagés entre mask_with_values
etmask_with_in1d
res.T.plot(loglog=True)
Les fonctions
def mask_standard(df):
mask = df['A'] == 'foo'
return df[mask]
def mask_standard_loc(df):
mask = df['A'] == 'foo'
return df.loc[mask]
def mask_with_values(df):
mask = df['A'].values == 'foo'
return df[mask]
def mask_with_values_loc(df):
mask = df['A'].values == 'foo'
return df.loc[mask]
def query(df):
return df.query('A == "foo"')
def xs_label(df):
return df.set_index('A', append=True, drop=False).xs('foo', level=-1)
def mask_with_isin(df):
mask = df['A'].isin(['foo'])
return df[mask]
def mask_with_in1d(df):
mask = np.in1d(df['A'].values, ['foo'])
return df[mask]
Essai
res = pd.DataFrame(
index=[
'mask_standard', 'mask_standard_loc', 'mask_with_values', 'mask_with_values_loc',
'query', 'xs_label', 'mask_with_isin', 'mask_with_in1d'
],
columns=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
dtype=float
)
for j in res.columns:
d = pd.concat([df] * j, ignore_index=True)
for i in res.index:a
stmt = '{}(d)'.format(i)
setp = 'from __main__ import d, {}'.format(i)
res.at[i, j] = timeit(stmt, setp, number=50)
Timing spécial
En regardant le cas spécial où nous avons un seul non-objet dtype
pour la trame de données entière.
Code ci-dessous
spec.div(spec.min())
10 30 100 300 1000 3000 10000 30000
mask_with_values 1.009030 1.000000 1.194276 1.000000 1.236892 1.095343 1.000000 1.000000
mask_with_in1d 1.104638 1.094524 1.156930 1.072094 1.000000 1.000000 1.040043 1.027100
reconstruct 1.000000 1.142838 1.000000 1.355440 1.650270 2.222181 2.294913 3.406735
Il s'avère que la reconstruction n'en vaut pas la peine après quelques centaines de rangées.
spec.T.plot(loglog=True)
Les fonctions
np.random.seed([3,1415])
d1 = pd.DataFrame(np.random.randint(10, size=(10, 5)), columns=list('ABCDE'))
def mask_with_values(df):
mask = df['A'].values == 'foo'
return df[mask]
def mask_with_in1d(df):
mask = np.in1d(df['A'].values, ['foo'])
return df[mask]
def reconstruct(df):
v = df.values
mask = np.in1d(df['A'].values, ['foo'])
return pd.DataFrame(v[mask], df.index[mask], df.columns)
spec = pd.DataFrame(
index=['mask_with_values', 'mask_with_in1d', 'reconstruct'],
columns=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
dtype=float
)
Essai
for j in spec.columns:
d = pd.concat([df] * j, ignore_index=True)
for i in spec.index:
stmt = '{}(d)'.format(i)
setp = 'from __main__ import d, {}'.format(i)
spec.at[i, j] = timeit(stmt, setp, number=50)