Python pandas удаляет повторяющиеся столбцы

126

Каков самый простой способ удалить повторяющиеся столбцы из фрейма данных?

Я читаю текстовый файл с повторяющимися столбцами через:

import pandas as pd

df=pd.read_table(fname)

Имена столбцов:

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

Все столбцы Time и Time Relative содержат одни и те же данные. Я хочу:

Time, Time Relative, N2, H2

Все мои попытки сбросить, удалить и т. Д., Например:

df=df.T.drop_duplicates().T

Привести к однозначным ошибкам индекса:

Reindexing only valid with uniquely valued index objects

Извините за то, что я новичок Панды. Мы ценим любые предложения.


дополнительные детали

Версия Pandas: 0.9.0
Версия Python: 2.7.3
Windows 7
(устанавливается через Pythonxy 2.7.3.0)

файл данных (примечание: в реальном файле столбцы разделены табуляцией, здесь они разделены 4 пробелами):

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
источник
Какая у вас версия панд? ( import pandas as pd; pd.__version__ )
beardc
1
@BirdJaguarIV, я использую pandas версии 0.9.0
Onlyjus
Вы можете попробовать обновиться до 0.10. Моя версия делает столбцы уникальными read_tableдля примера, который я составил.
beardc
Помните, что df = df.T.drop_duplicates (). T не учитывает имя столбца. Если у вас есть два столбца с одинаковыми данными, но разными именами, один будет удален по ошибке.
Joylove

Ответы:

392

Есть однострочное решение проблемы. Это применимо, если некоторые имена столбцов дублируются, и вы хотите их удалить:

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

Как это устроено:

Предположим, что столбцы фрейма данных ['alpha','beta','alpha']

df.columns.duplicated()возвращает логический массив: Trueили Falseдля каждого столбца. Если это так, Falseто имя столбца уникально до этого момента, если это так, Trueто имя столбца дублируется ранее. Например, используя данный пример, возвращаемое значение будет [False,False,True].

Pandasпозволяет индексировать с использованием логических значений, в результате чего выбираются только Trueзначения. Поскольку мы хотим сохранить недублированные столбцы, нам нужно перевернуть приведенный выше логический массив (т.е. [True, True, False] = ~[False,False,True])

Наконец, df.loc[:,[True,True,False]]выбирает только недублируемые столбцы с помощью вышеупомянутой возможности индексирования.

Примечание : приведенное выше проверяет только имена столбцов, но не значения столбцов.

Джин Буринский
источник
16
Идеальный ответ также будет работать для повторяющихся значений, а не только для имен.
GrimSqueaker 07
7
@GrimSqueaker: Если вы хотите решить, дублируются ли значения, вам нужно что-то вроде df.T.drop_duplicates().T.
Джон Цвинк
3
Безусловно, самое быстрое решение
AtotheSiv
2
@ VaidøtasIvøška, пожалуйста, посмотрите второй ответ на этот вопрос
Джин Бурински
2
@JohnZwinck: это работает только для небольших фреймов данных, так как существует ограничение на количество столбцов, которые вы можете иметь. Для меня это не удалось, например, для фрейма данных со 100000 строками, так как это дает 100000 столбцов после транспонирования, что невозможно
Элко ван Влит,
40

Похоже, вы уже знаете уникальные имена столбцов. Если это так, то df = df['Time', 'Time Relative', 'N2']сработает.

Если нет, ваше решение должно работать:

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

Вероятно, у вас есть что-то конкретное в ваших данных, что портит их. Мы могли бы оказать дополнительную помощь, если бы вы могли предоставить нам более подробную информацию о данных.

Изменить: Как сказал Энди, проблема, вероятно, связана с повторяющимися заголовками столбцов.

Для примера файла таблицы dummy.csv я составил:

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

использование read_tableдает уникальные столбцы и работает правильно:

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  

Если ваша версия не позволяет вам, вы можете придумать решение, чтобы сделать их уникальными:

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
источник
5
К сожалению, df['Time']выбирает все временные ряды (т.е. возвращает DataFrame), и df['Time', ..]это возвращает весь DataFrame.
Энди Хайден
Да, это довольно утомительно ... надеюсь, это просто разница в версиях.
beardc
2
Использование двойного транспонирования может иметь непредвиденные побочные эффекты, такие как преобразование числовых типов в объекты в случае, если у вас есть df со смешанными типами. См .: stackoverflow.com/questions/24682396/…
Петергавинкин
Это решение вызывает у меня проблемы с большими фреймами данных: RecursionError: maximum recursion depth exceeded
Скотт
Транспонирование большого фрейма данных будет медленным процессом
Куш Патель
13

Транспонирование неэффективно для больших фреймов данных. Вот альтернатива:

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       

Используйте это так:

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

редактировать

Версия с эффективным использованием памяти, которая обрабатывает nans как любое другое значение:

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
Калу
источник
3
Работает как шарм, очень эффективно! Использование зависало my_df.T.drop_duplicates().Tбы на больших фреймах данных.
Will
1
Прекрасное решение, но 26 апреля 2017 года я получил /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
Джордж Фишер
замена if array_equivalent(ia, ja):на, if np.array_equal(ia, ja):кажется, дает те же результаты, но я читал, что она плохо обрабатывает NaN.
Джордж Фишер
@GeorgeFisher Доступен ли базовый код для array_equivalentпубличного репо, возможно, в более старой ветке?
калу
@kalu теперь есть ток numpy.array_equiv; для панд, я не вижу веток более ранних pandas.core.common
Джордж Фишер,
12

Если я не ошибаюсь, следующее выполняет то, что было запрошено, без проблем с памятью решения транспонирования и с меньшим количеством строк, чем функция @kalu, сохраняя первый из любых столбцов с одинаковыми именами.

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)
Эллиот Коллинз
источник
Ваше решение не работает в моем случае, оно показывает мне: «ValueError: метки ['toDROP'] не содержатся в оси» после выполнения последней строки
NuValue
4

Похоже, вы были на правильном пути. Вот та строчка, которую вы искали:

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

Но поскольку нет примера фрейма данных, который генерирует указанное сообщение об ошибке Reindexing only valid with uniquely valued index objects, трудно сказать, что именно решило бы проблему. если для вас важно восстановление исходного индекса, сделайте следующее:

original_index = df.index.names
df.reset_index().T.drop_duplicates().reset_index(original_index).T
Тони Б.
источник
0

Первый шаг: - Прочтите первую строку, т.е. все столбцы, удалите все повторяющиеся столбцы.

Второй шаг: - Наконец прочтите только эти столбцы.

cols = pd.read_csv("file.csv", header=None, nrows=1).iloc[0].drop_duplicates()
df = pd.read_csv("file.csv", usecols=cols)
Камран Каусар
источник
0

Я столкнулся с этой проблемой, когда один лайнер, предоставленный первым ответом, работал хорошо. Однако у меня возникла дополнительная сложность, когда во второй копии столбца были все данные. Первый экземпляр не стал.

Решение заключалось в том, чтобы создать два фрейма данных, разделив один фрейм данных путем переключения оператора отрицания. Получив два фрейма данных, я запустил оператор соединения, используя расширение lsuffix. Таким образом, я мог бы ссылаться на столбец без данных и удалить его.

- E

Эхо Эдмунда
источник
0

Приведенный ниже способ идентифицирует дублирующиеся столбцы, чтобы проверить, что происходит не так при первоначальном построении фрейма данных.

dupes = pd.DataFrame(df.columns)
dupes[dupes.duplicated()]
Джо
источник