熊猫:如何简洁地去除列的子集

时间:2016-02-22 23:13:03

标签: python pandas

我想计算并减去列子集的平均值。这是一种方法:

#!/usr/bin/env python3
import numpy as np
import pandas as pd

def col_avg(df, col_ids):
    '''Calculate and subtract average over *col_ids*

    *df* is modified in-place.
    '''

    cols = [ df.columns[i] for i in col_ids ]
    acc = df[cols[0]].copy()
    for col in cols[1:]:
        acc += df[col]
    acc /= len(cols)
    for col in cols:
        df[col] -= acc

# Create example data
np.random.seed(42)
df = pd.DataFrame(data=np.random.random((433,80)) + np.arange(433)[:, np.newaxis],
                  columns=['col-%d' % x for x in range(80)])
#df = pd.DataFrame.from_csv('data.csv')

# Calculate average over columns 2, 3 and 6
df_old = df.copy()
col_avg(df, [ 1, 2, 5])
assert any(df_old.iloc[0] != df.iloc[0])

现在,我并不特别喜欢这两个for循环,所以我试着更简洁地表达同样的操作:

def col_avg(df, col_ids):
    dfT = df.T
    mean = dfT.iloc[col_ids].mean()
    dfT.iloc[col_ids] -= mean

这种实现看起来更好(IMO),但它有一个缺点:它只适用于某些数据集。通过上面的例子,它可以工作。但是例如加载this csv file时失败。

我唯一的解释是,在某些情况下,dfT.iloc[col_ids]表达式必须在内部创建值数组的副本,而不是就地修改它。

  • 这是正确的解释吗?
  • 如果是这样,DataFrame是什么让pandas决定在一个案例中复制数据而不是另一个案例?
  • 是否有其他方法可以执行始终有效且不需要显式迭代的任务?

编辑:在建议替代实施时,请说明您认为实施始终的原因。毕竟,上面的代码似乎也适用于某些输入。

3 个答案:

答案 0 :(得分:1)

DataFrame的转置dfT = df.T可能会返回一个新的DataFrame,而不是一个视图。 在这种情况下,修改dfTdf无效。

在您的玩具示例中,

df = pd.DataFrame(data=np.random.random((433,80)) + np.arange(433)[:, np.newaxis],
                  columns=['col-%d' % x for x in range(80)])

所有列都具有相同的dtype:

In [83]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 433 entries, 0 to 432
Data columns (total 80 columns):
col-0     433 non-null float64
col-1     433 non-null float64
col-2     433 non-null float64
...          
dtypes: float64(80)
memory usage: 274.0 KB

而在使用CSV构建的DataFrame中,某些列具有int64 dtype:

In [55]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 492 entries, 0 to 491
Data columns (total 72 columns):
sample no                       492 non-null int64
index                           492 non-null int64
plasma-r                        492 non-null float64
plasma-z                        492 non-null float64
...

DataFrame的列始终只有一个dtype。所以当你转置它时 基于CSV的df,无法通过简单地转置a来形成新的DataFrame 单个底层NumPy数组。整数列中的整数 现在遍布各行。 df.T的每个必须有一个dtype,所以 整数被上浮到浮点数。所以df.T的所有列都有dtype float64。 dtypes更改时必须复制数据。

底线是:因此当df具有混合类型时,df.T是副本。

col_avg可以简化为

def col_avg2(df, col_ids):
    means = df.iloc[:, col_ids].mean(axis=1)
    for i in col_ids:
        df.iloc[:, i] -= means

请注意,表达式 df.iloc[:, col_ids]将返回一个副本,因为cols_ids不是基本切片。但分配df.iloc[...](或df.loc[...])可以保证修改df

这就是为什么分配df.ilocdf.loc是避免assignment-with-chained-indexing pitfall的推荐方法。

答案 1 :(得分:0)

根据我对这个问题的理解,这就是你所要求的。我不明白你为什么要转置数据帧。注意:为简单起见,我删除了字符串列名称,但您可以轻松地替换它们。

np.random.seed(42)
df = pd.DataFrame(data=np.random.random((6,8)) + np.arange(6)[:, np.newaxis])#,
                  #columns=['col-%d' % x for x in range(80)])

# Calculate average over columns 2, 3 and 6
df_old = df.copy()
col_ids=[1,2,5]
df[col_ids] = df[col_ids] - np.mean(df[col_ids].values)
df_old-df # to make sure average is calculated over all three columns

Out[139]:
    0   1           2           3   4   5           6   7
0   0   2.950637    2.950637    0   0   2.950637    0   0
1   0   2.950637    2.950637    0   0   2.950637    0   0
2   0   2.950637    2.950637    0   0   2.950637    0   0
3   0   2.950637    2.950637    0   0   2.950637    0   0
4   0   2.950637    2.950637    0   0   2.950637    0   0
5   0   2.950637    2.950637    0   0   2.950637    0   0

答案 2 :(得分:0)

OP,假设您希望计算相似列的平均值并减去(比如&​​#34; Psi在......&#34;列)。最简单的方法是

df = pd.read_csv('data.csv')

psi_cols = [c for c in df.columns if c.startswith('Psi')]
df[psi_cols] -= df[psi_cols].mean().mean()

这计算所有列的总平均值。如果要从每列中减去列平均值,请执行

df[psi_cols] -= df[psi_cols].mean()