更有效的方法来清理字符串列并添加新列

时间:2016-12-21 18:34:33

标签: python pandas apply

我的数据框df包含['metric_type', 'metric_value']列。对于每一行,我想确保我有一个名称等于'metric_type'的列,该列的值等于'metric_value'

我的一个问题是'metric_type'有虚假的空间,我想摆脱它。

考虑数据框df

df = pd.DataFrame([
        ['a ', 1],
        [' b', 2],
        [' c ', 3]
    ], columns=['metric_type', 'metric_value'])

print(df)

  metric_type  metric_value
0          a              1
1           b             2
2          c              3

请注意,'metric_type'的每个值都有不同位置的空格。

我创建了一个使用apply的函数,但这需要花费很长时间。

def assign_metric_vals(row):
    row[row['metric_type'].replace(" ", "")] = row['metric_value']
    return row

当我使用它时,我明白了:

       a    b    c metric_type  metric_value
0 1.0000  nan  nan          a              1
1    nan 2.00  nan           b             2
2    nan  nan 3.00          c              3

是否有更好的(阅读,#34;更快")方式来完成同样的任务?

3 个答案:

答案 0 :(得分:11)

使用metric_type和取消堆栈设置索引会更好。

df.set_index(df.metric_type.str.replace(' ', ''), append=True).metric_value.unstack()

演示

df = pd.DataFrame([
        ['a ', 1],
        [' b', 2],
        [' c ', 3]
    ], columns=['metric_type', 'metric_value'])

print(df)

  metric_type  metric_value
0          a              1
1           b             2
2          c              3

print(df.apply(assign_metric_vals, 1))

       a    b    c metric_type  metric_value
0 1.0000  nan  nan          a              1
1    nan 2.00  nan           b             2
2    nan  nan 3.00          c              3

或我的方式

idx = df.metric_type.str.replace(' ', '')
d1 = df.set_index(idx, append=True).metric_value.unstack()
print(pd.concat([d1, df], axis=1))

       a    b    c metric_type  metric_value
0 1.0000  nan  nan          a              1
1    nan 2.00  nan           b             2
2    nan  nan 3.00          c              3

时间

使用更大的df
df1 = pd.concat([df] * 30000, ignore_index=True)

%%timeit
idx = df1.metric_type.str.replace(' ', '')
d1 = df1.set_index(idx, append=True).metric_value.unstack()
pd.concat([d1, df1], axis=1)
  

10个循环,最佳3:每循环77.3毫秒

%%timeit
df1.apply(assign_metric_vals, 1)
  

1个循环,最佳3:每循环57.4秒

答案 1 :(得分:2)

这是一种替代方案,速度提高约20%,并提供与@ piRSquared相同的答案。我不会建议它更好或更差(一般情况下),但赏金是在接受答案后发布的,所以我会将此作为附加选项提供。

%%timeit
idx = df1.metric_type.str.replace(' ', '')
d1 = df1.set_index(idx, append=True).metric_value.unstack()
result1 = pd.concat([d1, df1], axis=1)
10 loops, best of 3: 97.6 ms per loop

%%timeit 
df1.metric_type = df1.metric_type.str.strip()
d1 = df1.pivot(columns='metric_type', values='metric_value')
result2 = pd.concat([d1, df1], axis=1)
10 loops, best of 3: 77.2 ms per loop

大约1/3的速度提升来自使用strip而不是replace,而使用pivot代替unstack则是2/3。 (无论如何,concat步骤是相同且非常快的。)

答案 2 :(得分:2)

查看最终数据框的创建方式,字符串列的单热编码在与其他数据框相比时的整体性能方面确实看起来不是一个坏主意迄今提到的方法。

<强> 步骤:

  1. pd.get_dummies系列上使用metric_type,从分类变量中创建虚拟变量。这一部分加上str.strip是该批次中最耗时的部分。

  2. 不是直接在系列对象上剥离前导/尾随空白字符,而是计算get_dummies部分,因为很有可能在系列中重复了一些分类变量稍后将在虚拟创建期间共享相同的列。重复变量越多,过滤掉这些额外空间所花费的时间就越少。仅对获取的虚拟变量str.strip的列执行DF。这种方法节省了大量时间。

  3. 对获得的这些列进行排序,使其按字典顺序排序,并将重复的列(如果存在)放在彼此相邻的位置。允许根据这些列组合修改DF
  4. 使用带有np.unique参数的return_index=True来提取存在的唯一列以及相应的索引。
  5. 我们需要找到一种方法将相同的列分组到一个健康的列中。为此,我们可以使用np.add.reduceat,其工作方式类似于groupby操作(相当于 - df.groupby(df.columns.tolist(), axis=1).sum()),但具有真正快速的专长。要配对的索引由idx np.unique提供。值的减少发生在这些切片上,并且它们的运行总和在列(axis=1)之间计算。
  6. dtype返回bool,这有助于我们使用np.where,因为它的功能类似于布尔掩码,其中1&#39; s / 0被映射到{{分别是1}} / True。然后,这些1由False系列中的值和metric_value的0来填充。
  7. 我们的NaN已准备就绪,需要与原始的DF列开始连接,从而产生最终清理的数据框。
  8. 解决方案:

    DF

    <强> 时序:

    def dummies_strip_concat(df):
        one_hot_enc = pd.get_dummies(df.metric_type)
        one_hot_enc.columns = one_hot_enc.columns.str.strip()
        one_hot_enc.sortlevel(axis=1, inplace=True)
        a, idx = np.unique(one_hot_enc.columns.values, return_index=True)
        out = np.where(np.add.reduceat(one_hot_enc.values, idx, axis=1, dtype=np.bool), 
                       df.metric_value.values[:, None], 
                       np.nan)
        return (pd.concat([pd.DataFrame(out, df.index, a), df], axis=1))
    

    对于包含几千行的def pir(df): idx = df.metric_type.str.replace(' ', '') d1 = df.set_index(idx, append=True).metric_value.unstack() return pd.concat([d1, df], axis=1) def johne(df): df.metric_type = df.metric_type.str.strip() d1 = df.pivot(columns='metric_type', values='metric_value') return pd.concat([d1, df], axis=1) ,与OP的想法相比:

    DF