pandy Pythona usuwają zduplikowane kolumny

126

Jaki jest najłatwiejszy sposób usunięcia zduplikowanych kolumn z ramki danych?

Czytam plik tekstowy, który ma zduplikowane kolumny za pośrednictwem:

import pandas as pd

df=pd.read_table(fname)

Nazwy kolumn to:

Time, Time Relative, N2, Time, Time Relative, H2, etc...

Wszystkie kolumny Time i Time Relative zawierają te same dane. Chcę:

Time, Time Relative, N2, H2

Wszystkie moje próby upuszczania, usuwania itp., Takie jak:

df=df.T.drop_duplicates().T

Skutkuje unikalnymi wartościami błędów indeksu:

Reindexing only valid with uniquely valued index objects

Przepraszam, że jestem noobem Pandas. Wszelkie sugestie będą mile widziane.


Dodatkowe Szczegóły

Wersja Pandy: 0.9.0
Wersja Pythona: 2.7.3
Windows 7
(zainstalowana przez Pythonxy 2.7.3.0)

plik danych (uwaga: w rzeczywistym pliku kolumny są oddzielone tabulatorami, tutaj są oddzielone 4 spacjami):

Time    Time Relative [s]    N2[%]    Time    Time Relative [s]    H2[ppm]
2/12/2013 9:20:55 AM    6.177    9.99268e+001    2/12/2013 9:20:55 AM    6.177    3.216293e-005    
2/12/2013 9:21:06 AM    17.689    9.99296e+001    2/12/2013 9:21:06 AM    17.689    3.841667e-005    
2/12/2013 9:21:18 AM    29.186    9.992954e+001    2/12/2013 9:21:18 AM    29.186    3.880365e-005    
... etc ...
2/12/2013 2:12:44 PM    17515.269    9.991756+001    2/12/2013 2:12:44 PM    17515.269    2.800279e-005    
2/12/2013 2:12:55 PM    17526.769    9.991754e+001    2/12/2013 2:12:55 PM    17526.769    2.880386e-005
2/12/2013 2:13:07 PM    17538.273    9.991797e+001    2/12/2013 2:13:07 PM    17538.273    3.131447e-005
Onlyjus
źródło
Jaką masz wersję pand? ( import pandas as pd; pd.__version__ )
beardc
1
@BirdJaguarIV, używam pandy w wersji 0.9.0
Onlyjus
Możesz spróbować zaktualizować do wersji 0.10. Moja wersja sprawia, że ​​kolumny są wyjątkowe read_tablew przykładzie, który stworzyłem.
beardc
Uważaj, że df = df.T.drop_duplicates (). T nie bierze pod uwagę nazwy kolumny. Jeśli masz dwie kolumny z tymi samymi danymi, ale różnymi nazwami, jedna zostanie omyłkowo usunięta.
Joylove

Odpowiedzi:

392

Istnieje jednowierszowe rozwiązanie problemu. Dotyczy to sytuacji, gdy nazwy niektórych kolumn są zduplikowane i chcesz je usunąć:

df = df.loc[:,~df.columns.duplicated()]

Jak to działa:

Załóżmy, że kolumny ramki danych to ['alpha','beta','alpha']

df.columns.duplicated()zwraca tablicę logiczną: a Truelub Falsedla każdej kolumny. Jeśli tak, to Falsenazwa kolumny jest unikalna do tego momentu, jeśli tak, to Truenazwa kolumny jest zduplikowana wcześniej. Na przykład, używając podanego przykładu, zwrócona wartość będzie [False,False,True].

Pandasumożliwia indeksowanie przy użyciu wartości boolowskich, przy czym wybiera tylko Truewartości. Ponieważ chcemy zachować nieuplikowane kolumny, potrzebujemy odwrócenia powyższej tablicy boolowskiej (tj. [True, True, False] = ~[False,False,True])

Na koniec df.loc[:,[True,True,False]]wybiera tylko kolumny, które nie zostały zduplikowane, korzystając ze wspomnianej możliwości indeksowania.

Uwaga : powyższe sprawdza tylko nazwy kolumn, a nie wartości kolumn.

Gene Burinsky
źródło
16
Idealna odpowiedź działałaby również w przypadku zduplikowanych wartości, a nie tylko nazw.
GrimSqueaker
7
@GrimSqueaker: Jeśli chcesz rozważyć, czy wartości są zduplikowane, potrzebujesz czegoś takiego df.T.drop_duplicates().T.
John Zwinck,
3
Zdecydowanie najszybsze rozwiązanie
AtotheSiv
2
@ VaidøtasIvøška, zobacz drugą odpowiedź na to pytanie
Gene Burinsky
2
@JohnZwinck: działa to tylko w przypadku małych ramek danych, ponieważ istnieje ograniczenie liczby kolumn, które możesz mieć. Dla mnie nie udało się na przykład dla ramki danych zawierającej 100 000 wierszy, ponieważ po transpozycji daje to 100 000 kolumn, co nie jest możliwe
Eelco van Vliet
40

Wygląda na to, że znasz już unikalne nazwy kolumn. Jeśli tak jest, to df = df['Time', 'Time Relative', 'N2']zadziała.

Jeśli nie, Twoje rozwiązanie powinno działać:

In [101]: vals = np.random.randint(0,20, (4,3))
          vals
Out[101]:
array([[ 3, 13,  0],
       [ 1, 15, 14],
       [14, 19, 14],
       [19,  5,  1]])

In [106]: df = pd.DataFrame(np.hstack([vals, vals]), columns=['Time', 'H1', 'N2', 'Time Relative', 'N2', 'Time'] )
          df
Out[106]:
   Time  H1  N2  Time Relative  N2  Time
0     3  13   0              3  13     0
1     1  15  14              1  15    14
2    14  19  14             14  19    14
3    19   5   1             19   5     1

In [107]: df.T.drop_duplicates().T
Out[107]:
   Time  H1  N2
0     3  13   0
1     1  15  14
2    14  19  14
3    19   5   1

Prawdopodobnie masz coś specyficznego dla swoich danych, co je psuje. Moglibyśmy udzielić większej pomocy, gdybyś mógł podać nam więcej szczegółów na temat danych.

Edycja: Jak powiedział Andy, problem prawdopodobnie dotyczy zduplikowanych tytułów kolumn.

Dla przykładowego pliku tabeli „dummy.csv” przygotowałem:

Time    H1  N2  Time    N2  Time Relative
3   13  13  3   13  0
1   15  15  1   15  14
14  19  19  14  19  14
19  5   5   19  5   1

użycie read_tabledaje unikalne kolumny i działa poprawnie:

In [151]: df2 = pd.read_table('dummy.csv')
          df2
Out[151]:
         Time  H1  N2  Time.1  N2.1  Time Relative
      0     3  13  13       3    13              0
      1     1  15  15       1    15             14
      2    14  19  19      14    19             14
      3    19   5   5      19     5              1
In [152]: df2.T.drop_duplicates().T
Out[152]:
             Time  H1  Time Relative
          0     3  13              0
          1     1  15             14
          2    14  19             14
          3    19   5              1  

Jeśli twoja wersja na to nie pozwala, możesz zhakować rozwiązanie, aby uczynić je wyjątkowymi:

In [169]: df2 = pd.read_table('dummy.csv', header=None)
          df2
Out[169]:
              0   1   2     3   4              5
        0  Time  H1  N2  Time  N2  Time Relative
        1     3  13  13     3  13              0
        2     1  15  15     1  15             14
        3    14  19  19    14  19             14
        4    19   5   5    19   5              1
In [171]: from collections import defaultdict
          col_counts = defaultdict(int)
          col_ix = df2.first_valid_index()
In [172]: cols = []
          for col in df2.ix[col_ix]:
              cnt = col_counts[col]
              col_counts[col] += 1
              suf = '_' + str(cnt) if cnt else ''
              cols.append(col + suf)
          cols
Out[172]:
          ['Time', 'H1', 'N2', 'Time_1', 'N2_1', 'Time Relative']
In [174]: df2.columns = cols
          df2 = df2.drop([col_ix])
In [177]: df2
Out[177]:
          Time  H1  N2 Time_1 N2_1 Time Relative
        1    3  13  13      3   13             0
        2    1  15  15      1   15            14
        3   14  19  19     14   19            14
        4   19   5   5     19    5             1
In [178]: df2.T.drop_duplicates().T
Out[178]:
          Time  H1 Time Relative
        1    3  13             0
        2    1  15            14
        3   14  19            14
        4   19   5             1 
beardc
źródło
5
Niestety df['Time']wybiera wszystkie szeregi czasowe (tj. Zwraca DataFrame), a df['Time', ..]to zwróci cały DataFrame.
Andy Hayden,
Tak, to dość nudne ... miejmy nadzieję, że to tylko różnica wersji.
beardc
2
Używanie podwójnych transpozycji może mieć niezamierzone efekty uboczne, takie jak konwersja typów liczbowych na obiekty w przypadku, gdy masz plik df z typami mieszanymi. Zobacz: stackoverflow.com/questions/24682396/…
Petergavinkin
To rozwiązanie daje mi problemy w przypadku dużych ramek danych: RecursionError: maximum recursion depth exceeded
Scott
Transpozycja dużej ramki danych będzie procesem powolnym
Kush Patel
13

Transpozycja jest nieefektywna w przypadku dużych ramek danych. Oto alternatywa:

def duplicate_columns(frame):
    groups = frame.columns.to_series().groupby(frame.dtypes).groups
    dups = []
    for t, v in groups.items():
        dcols = frame[v].to_dict(orient="list")

        vs = dcols.values()
        ks = dcols.keys()
        lvs = len(vs)

        for i in range(lvs):
            for j in range(i+1,lvs):
                if vs[i] == vs[j]: 
                    dups.append(ks[i])
                    break

    return dups       

Użyj tego w ten sposób:

dups = duplicate_columns(frame)
frame = frame.drop(dups, axis=1)

Edytować

Wersja oszczędzająca pamięć, która traktuje nans jak każdą inną wartość:

from pandas.core.common import array_equivalent

def duplicate_columns(frame):
    groups = frame.columns.to_series().groupby(frame.dtypes).groups
    dups = []

    for t, v in groups.items():

        cs = frame[v].columns
        vs = frame[v]
        lcs = len(cs)

        for i in range(lcs):
            ia = vs.iloc[:,i].values
            for j in range(i+1, lcs):
                ja = vs.iloc[:,j].values
                if array_equivalent(ia, ja):
                    dups.append(cs[i])
                    break

    return dups
kalu
źródło
3
Działa jak urok, bardzo wydajnie! Użycie my_df.T.drop_duplicates().Tzawiesiłoby się na dużych ramkach danych.
Will
1
Cudowne rozwiązanie, ale 26 kwietnia 2017 dostałem/usr/local/lib/python3.5/dist-packages/ipykernel_launcher.py:17: DeprecationWarning: 'pandas.core.common.array_equivalent' is deprecated and is no longer public API
George Fisher
zastępując if array_equivalent(ia, ja):ze if np.array_equal(ia, ja):wydaje się produkować takie same wyniki, ale czytałem, że nie obsługuje Nans dobrze.
George Fisher
@GeorgeFisher Czy kod źródłowy jest array_equivalentnadal dostępny w publicznym repozytorium, prawdopodobnie w starszej gałęzi?
kalu
@kalu jest teraz prądem numpy.array_equiv; w przypadku pand nie widzę żadnych wcześniejszych gałęzi wydań na GitHubie, pandas.core.commonale być może są inne miejsca, w których można szukać
George Fisher
11

Jeśli się nie mylę, poniższe robi to, o co pytano, bez problemów z pamięcią rozwiązania transponującego iz mniejszą liczbą wierszy niż funkcja @kalu, zachowując pierwszą z podobnie nazwanych kolumn.

Cols = list(df.columns)
for i,item in enumerate(df.columns):
    if item in df.columns[:i]: Cols[i] = "toDROP"
df.columns = Cols
df = df.drop("toDROP",1)
Elliott Collins
źródło
Twoje rozwiązanie nie działa w moim przypadku, pokazuje mi: "ValueError: labels ['toDROP'] nie zawarte w osi" po wykonaniu ostatniej linii
NuValue Kwietnia
4

Wygląda na to, że byłeś na dobrej drodze. Oto jeden wiersz, którego szukałeś:

df.reset_index().T.drop_duplicates().T

Ale ponieważ nie ma przykładowej ramki danych, która generuje przywoływany komunikat o błędzie Reindexing only valid with uniquely valued index objects, trudno jest dokładnie powiedzieć, co dokładnie rozwiązałoby problem. jeśli przywracanie oryginalnego indeksu jest dla Ciebie ważne, wykonaj następujące czynności:

original_index = df.index.names
df.reset_index().T.drop_duplicates().reset_index(original_index).T
Tony B.
źródło
0

Pierwszy krok: - Przeczytaj pierwszy wiersz, tj. Wszystkie kolumny, usuń wszystkie zduplikowane kolumny.

Drugi krok: - Na koniec przeczytaj tylko te kolumny.

cols = pd.read_csv("file.csv", header=None, nrows=1).iloc[0].drop_duplicates()
df = pd.read_csv("file.csv", usecols=cols)
kamran kausar
źródło
0

Natknąłem się na ten problem, w którym jedna wkładka dostarczona przez pierwszą odpowiedź działała dobrze. Jednak miałem dodatkową komplikację polegającą na tym, że druga kopia kolumny zawierała wszystkie dane. Pierwsza kopia nie.

Rozwiązaniem było utworzenie dwóch ramek danych przez podzielenie jednej ramki danych poprzez przełączenie operatora negacji. Gdy miałem już dwie ramki danych, uruchomiłem instrukcję złączenia przy użyciu lsuffix. W ten sposób mogłem odwołać się i usunąć kolumnę bez danych.

- E.

Echo Edmunda
źródło
0

Poniższy sposób pozwoli zidentyfikować kolumny dupe, aby sprawdzić, co pierwotnie było nie tak podczas tworzenia ramki danych.

dupes = pd.DataFrame(df.columns)
dupes[dupes.duplicated()]
Joe
źródło