Jak usunąć kolumnę zawierającą tylko zera w Pandach?

87

Obecnie mam ramkę danych składającą się z kolumn z 1 i 0 jako wartościami, chciałbym iterować przez kolumny i usuwać te, które składają się tylko z 0. Oto, czego próbowałem do tej pory:

ones = []
zeros = []
for year in years:
    for i in range(0,599):
        if year[str(i)].values.any() == 1:
            ones.append(i)
        if year[str(i)].values.all() == 0:
            zeros.append(i)
    for j in ones:
        if j in zeros:
            zeros.remove(j)
    for q in zeros:
        del year[str(q)]

W których latach jest lista ramek danych z różnych lat, które analizuję, jedynki to kolumny zawierające jedynkę, a zera to lista kolumn zawierających same zera. Czy istnieje lepszy sposób na usunięcie kolumny na podstawie warunku? Z jakiegoś powodu muszę sprawdzić, czy kolumny jedynek również znajdują się na liście zer i usunąć je z listy zer, aby uzyskać listę wszystkich kolumn zerowych.

user2587593
źródło

Odpowiedzi:

214
df.loc[:, (df != 0).any(axis=0)]

Oto zestawienie tego, jak to działa:

In [74]: import pandas as pd

In [75]: df = pd.DataFrame([[1,0,0,0], [0,0,1,0]])

In [76]: df
Out[76]: 
   0  1  2  3
0  1  0  0  0
1  0  0  1  0

[2 rows x 4 columns]

df != 0tworzy wartość logiczną DataFrame, która ma wartość True, gdzie dfjest różna od zera:

In [77]: df != 0
Out[77]: 
       0      1      2      3
0   True  False  False  False
1  False  False   True  False

[2 rows x 4 columns]

(df != 0).any(axis=0)zwraca serię logiczną wskazującą, które kolumny mają niezerowe wpisy. ( anyOperacja agreguje wartości wzdłuż osi 0 - tj. Wzdłuż wierszy - w jedną wartość logiczną. Dlatego wynikiem jest jedna wartość logiczna dla każdej kolumny).

In [78]: (df != 0).any(axis=0)
Out[78]: 
0     True
1    False
2     True
3    False
dtype: bool

I df.locmożna ich użyć do wybrania tych kolumn:

In [79]: df.loc[:, (df != 0).any(axis=0)]
Out[79]: 
   0  2
0  1  0
1  0  1

[2 rows x 2 columns]

Aby „usunąć” kolumny zerowe, przypisz ponownie df:

df = df.loc[:, (df != 0).any(axis=0)]
unutbu
źródło
Próbuję upuścić kolumnę, jeśli ma w niej 0 lub 1 i daje błąd: df = df.loc [:, (df! = 0 & df! = 1) .any (axis = 0)]
morfeusz
1
df.loc[:, (~df.isin([0,1])).any(axis=0)]również zadziała.
unutbu
1
@IgorFobia: Wiele rzeczy jest fałszywych bez 0. Na przykład puste łańcuchy lub None lub NaN. Aby zademonstrować różnicę, if df = pd.DataFrame([[np.nan]*10]), df.loc[:, df.any(axis=0)]zwraca wtedy pustą ramkę DataFrame, a df.loc[:, (df != 0).any(axis=0)]zwraca ramkę DataFrame z 10 kolumnami.
unutbu
4
Uważam, że łatwiej jest to zrozumieć, jeśli sprawdzimy, czy warunek jest prawdziwy, zamiast sprawdzać, czy warunek, który nie jest prawdziwy, nigdy nie jest spełniony. Myślę, że (df == 0).all(axis=0)jest to prostsze.
Ryszard Cetnarski
2
Dzięki za awarię. To wszystko było bardzo jasne.
Regi Mathew
7

Oto alternatywny sposób użycia is

df.replace(0,np.nan).dropna(axis=1,how="all")

W porównaniu z rozwiązaniem unutbu ten sposób jest oczywiście wolniejszy:

%timeit df.loc[:, (df != 0).any(axis=0)]
652 µs ± 5.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit df.replace(0,np.nan).dropna(axis=1,how="all")
1.75 ms ± 9.49 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Jeremy Z
źródło
0

Jeśli chcesz uzyskać bardziej wyrazisty sposób uzyskiwania nazw kolumn zerowych, abyś mógł je wydrukować / zarejestrować i upuścić je na miejscu według ich nazw :

zero_cols = [ col for col, is_zero in ((df == 0).sum() == df.shape[0]).items() if is_zero ]
df.drop(zero_cols, axis=1, inplace=True)

Niektóre załamują się:

# a pandas Series with {col: is_zero} items
# is_zero is True when the number of zero items in that column == num_all_rows
(df == 0).sum() == df.shape[0])

# a list comprehension of zero_col_names is built from the_series
[ col for col, is_zero in the_series.items() if is_zero ]
mork
źródło