2014-12-03 15 views
29

Ho un dataframe:panda: modo migliore per selezionare tutte le colonne che iniziano per X

import pandas as pd 
import numpy as np 

df = pd.DataFrame({'foo.aa': [1, 2.1, np.nan, 4.7, 5.6, 6.8], 
        'foo.fighters': [0, 1, np.nan, 0, 0, 0], 
        'foo.bars': [0, 0, 0, 0, 0, 1], 
        'bar.baz': [5, 5, 6, 5, 5.6, 6.8], 
        'foo.fox': [2, 4, 1, 0, 0, 5], 
        'nas.foo': ['NA', 0, 1, 0, 0, 0], 
        'foo.manchu': ['NA', 0, 0, 0, 0, 0],}) 

Voglio selezionare i valori di 1 in colonne a partire da foo.. C'è un modo migliore per farlo diverso:

df2 = df[(df['foo.aa'] == 1)| 
(df['foo.fighters'] == 1)| 
(df['foo.bars'] == 1)| 
(df['foo.fox'] == 1)| 
(df['foo.manchu'] == 1) 
] 

Qualcosa di simile a scrivere qualcosa di simile:

df2= df[df.STARTS_WITH_FOO == 1] 

La risposta dovrebbe stampare un dataframe come questo:

bar.baz foo.aa foo.bars foo.fighters foo.fox foo.manchu nas.foo 
0  5.0  1.0   0    0  2   NA  NA 
1  5.0  2.1   0    1  4   0  0 
2  6.0  NaN   0   NaN  1   0  1 
5  6.8  6.8   1    0  5   0  0 

[4 rows x 7 columns] 

risposta

45

Proprio eseguire un elenco di comprensione per creare le colonne:

In [28]: 

filter_col = [col for col in df if col.startswith('foo')] 
filter_col 
Out[28]: 
['foo.aa', 'foo.bars', 'foo.fighters', 'foo.fox', 'foo.manchu'] 
In [29]: 

df[filter_col] 
Out[29]: 
    foo.aa foo.bars foo.fighters foo.fox foo.manchu 
0  1.0   0    0  2   NA 
1  2.1   0    1  4   0 
2  NaN   0   NaN  1   0 
3  4.7   0    0  0   0 
4  5.6   0    0  0   0 
5  6.8   1    0  5   0 

Un altro metodo è quello di creare una serie dalle colonne e utilizzare il metodo str Vectorised startswith:

In [33]: 

df[df.columns[pd.Series(df.columns).str.startswith('foo')]] 
Out[33]: 
    foo.aa foo.bars foo.fighters foo.fox foo.manchu 
0  1.0   0    0  2   NA 
1  2.1   0    1  4   0 
2  NaN   0   NaN  1   0 
3  4.7   0    0  0   0 
4  5.6   0    0  0   0 
5  6.8   1    0  5   0 

Al fine di ottenere ciò che si vuole è necessario aggiungere il seguente per filtrare i valori che non soddisfano la tua ==1 criteri:

In [36]: 

df[df[df.columns[pd.Series(df.columns).str.startswith('foo')]]==1] 
Out[36]: 
    bar.baz foo.aa foo.bars foo.fighters foo.fox foo.manchu nas.foo 
0  NaN  1  NaN   NaN  NaN  NaN  NaN 
1  NaN  NaN  NaN    1  NaN  NaN  NaN 
2  NaN  NaN  NaN   NaN  1  NaN  NaN 
3  NaN  NaN  NaN   NaN  NaN  NaN  NaN 
4  NaN  NaN  NaN   NaN  NaN  NaN  NaN 
5  NaN  NaN   1   NaN  NaN  NaN  NaN 

EDIT

OK dopo aver visto ciò che si desidera che la risposta contorta è questa:

In [72]: 

df.loc[df[df[df.columns[pd.Series(df.columns).str.startswith('foo')]] == 1].dropna(how='all', axis=0).index] 
Out[72]: 
    bar.baz foo.aa foo.bars foo.fighters foo.fox foo.manchu nas.foo 
0  5.0  1.0   0    0  2   NA  NA 
1  5.0  2.1   0    1  4   0  0 
2  6.0  NaN   0   NaN  1   0  1 
5  6.8  6.8   1    0  5   0  0 
+0

perche non spostare l'opzione2 verso l'alto della tua risposta – JanLauGe

+0

cura downvoter per spiegare? – EdChum

+0

@JanLauGe sebbene 'startswith' sarebbe il puro metodo' pandas', l'utilizzo di una list comprehension è in realtà il metodo più veloce, quindi ho pubblicato entrambi i metodi – EdChum

17

Ora che i panda indici supportano operazioni sulle stringhe, probabilmente il modo più semplice e migliore per selezionare le colonne che iniziano con 'foo' è solo:

df.loc[:, df.columns.str.startswith('foo')] 

In alternativa, è possibile filtrare colonna (o riga) etichette con df.filter(). Per specificare un'espressione regolare per abbinare i nomi che iniziano con foo.:

>>> df.filter(regex=r'^foo\.', axis=1) 
    foo.aa foo.bars foo.fighters foo.fox foo.manchu 
0  1.0   0    0  2   NA 
1  2.1   0    1  4   0 
2  NaN   0   NaN  1   0 
3  4.7   0    0  0   0 
4  5.6   0    0  0   0 
5  6.8   1    0  5   0 

Per selezionare solo le righe necessarie (contenente un 1) e le colonne, è possibile utilizzare loc, selezionando le colonne utilizzando filter (o qualsiasi altro metodo) e le righe utilizzando any:

>>> df.loc[(df == 1).any(axis=1), df.filter(regex=r'^foo\.', axis=1).columns] 
    foo.aa foo.bars foo.fighters foo.fox foo.manchu 
0  1.0   0    0  2   NA 
1  2.1   0    1  4   0 
2  NaN   0   NaN  1   0 
5  6.8   1    0  5   0 
+0

e la parte di selezione? dove si applica df [cols] == 1, dovrei semplicemente fare un ciclo for o c'è un modo più veloce? – ccsv

+0

@ccsv Penso che potresti semplicemente scrivere 'df.filter (regex = r '^ foo \.', Axis = 1) == 1' (fammi sapere se ho frainteso ciò che vuoi). –

+0

è vicino hai solo bisogno di convertire il booleano e rimuovere alcune righe. Se si esegue il codice ho esso ha tutte le colonne sul posto ma ha solo righe 0,1,2,5 perché avevano il valore di 1 sulla colonna con il titolo 'pippo' – ccsv

1

La mia soluzione.Si può essere più lenta sulle prestazioni:

a = pd.concat(df[df[c] == 1] for c in df.columns if c.startswith('foo')) 
a.sort_index() 


    bar.baz foo.aa foo.bars foo.fighters foo.fox foo.manchu nas.foo 
0  5.0  1.0   0    0  2   NA  NA 
1  5.0  2.1   0    1  4   0  0 
2  6.0  NaN   0   NaN  1   0  1 
5  6.8  6.8   1    0  5   0  0 
1

Un'altra opzione per la selezione delle voci desiderate è quella di utilizzare map:

df.loc[(df == 1).any(axis=1), df.columns.map(lambda x: x.startswith('foo'))] 

che vi dà tutte le colonne per le righe che contengono un 1:

foo.aa foo.bars foo.fighters foo.fox foo.manchu 
0  1.0   0    0  2   NA 
1  2.1   0    1  4   0 
2  NaN   0   NaN  1   0 
5  6.8   1    0  5   0 

Il fila selezione avviene

(df == 1).any(axis=1) 

come nella risposta di @ AJCR che ti dà:

0  True 
1  True 
2  True 
3 False 
4 False 
5  True 
dtype: bool 

il che significa che fila 3 e 4 non contengono un 1 e non saranno selezionati.

La selezione delle colonne viene fatto usando l'indicizzazione booleana come questo:

df.columns.map(lambda x: x.startswith('foo')) 

Nell'esempio sopra questo restituisce

array([False, True, True, True, True, True, False], dtype=bool) 

Quindi, se una colonna non inizia con foo, False viene restituito e la colonna non è quindi selezionata.

Se si desidera solo per restituire tutte le righe che contengono un 1 - come l'output desiderato suggerisce - si può semplicemente fare

df.loc[(df == 1).any(axis=1)] 

che restituisce

bar.baz foo.aa foo.bars foo.fighters foo.fox foo.manchu nas.foo 
0  5.0  1.0   0    0  2   NA  NA 
1  5.0  2.1   0    1  4   0  0 
2  6.0  NaN   0   NaN  1   0  1 
5  6.8  6.8   1    0  5   0  0 
Problemi correlati