python pandas 删除重复列

308 投票
16 回答
475181 浏览
提问于 2025-04-17 16:31

如何从数据表中最简单地去掉重复的列呢?

我正在读取一个文本文件,里面有重复的列,代码是:

import pandas as pd

df=pd.read_table(fname)

这些列的名字是:

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

所有的“时间”和“相对时间”列里面的数据都是一样的。我想要的是:

Time, Time Relative, N2, H2

我尝试过很多方法,比如删除、去掉等等,像这样:

df=df.T.drop_duplicates().T

结果却总是出现唯一值索引错误:

Reindexing only valid with uniquely valued index objects

抱歉,我对Pandas还不太熟悉。有什么建议吗?


附加信息

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

16 个回答

19

转置对于大型数据框来说效率不高。这里有一个替代的方法:

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
50

听起来你已经知道了独特的列名。如果是这样的话,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

可能是你的数据里有一些特别的地方导致了问题。如果你能提供更多关于数据的细节,我们可以给你更多的帮助。

编辑:正如Andy说的,问题可能出在重复的列名上。

我随便做了一个示例表格文件 '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 
800

这里有一个一行代码的解决方案,可以根据重复的列名来删除列:

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

它是如何工作的:

假设数据框的列名是 ['alpha','beta','alpha']

df.columns.duplicated() 会返回一个布尔数组:每一列对应一个 TrueFalse。如果是 False,说明这个列名在之前是唯一的;如果是 True,说明这个列名之前已经出现过了。例如,在这个例子中,返回的值会是 [False,False,True]

Pandas 允许使用布尔值来索引,这样它只会选择 True 的值。因为我们想保留没有重复的列,所以需要把上面的布尔数组反转一下(也就是 [True, True, False] = ~[False,False,True])。

最后,df.loc[:,[True,True,False]] 就是用前面提到的索引方法来选择那些没有重复的列。

最后的 .copy() 是为了复制这个数据框,主要是为了避免后面修改现有数据框时出现错误。

注意:上面的操作只检查列名,而检查列的值。

要删除重复的索引

因为操作类似,所以对索引也可以做同样的处理:

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

通过检查值来删除重复项,而不进行转置

更新和注意事项:在应用这个方法时请小心。根据 DrWhat 在评论中提供的反例,这个解决方案在所有情况下可能不会得到想要的结果。

df = df.loc[:,~df.apply(lambda x: x.duplicated(),axis=1).all()].copy()

这样可以避免转置的问题。快吗?不快。有效吗?在某些情况下有效。这里,试试这个:

# create a large(ish) dataframe
ldf = pd.DataFrame(np.random.randint(0,100,size= (736334,1312))) 


#to see size in gigs
#ldf.memory_usage().sum()/1e9 #it's about 3 gigs

# duplicate a column
ldf.loc[:,'dup'] = ldf.loc[:,101]

# take out duplicated columns by values
ldf = ldf.loc[:,~ldf.apply(lambda x: x.duplicated(),axis=1).all()].copy()

撰写回答