【问题标题】:More efficient way to clean a column of strings and add a new column更有效的方法来清理一列字符串并添加一个新列
【发布时间】:2017-05-07 06:46:32
【问题描述】:

我有一个数据框 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

有没有更好(读作“更快”)的方法来完成同样的任务?

【问题讨论】:

  • apply 本身就很慢。它基本上是一个 python-for-loop 的包装器。
  • 无论如何,看起来你想要完成的任何事情都应该能够通过基本的pandas 分配来完成。你为什么不描述你想要完成的事情
  • 如果这么简单,我就不会发布这个问题了。如果对我要完成的工作有误解,我深表歉意。有一列 metric_type 包含我转换为列的值。然后我需要将 metric_value 列的行分配给正确转换的 metric_type 列。
  • 在底部添加了说明。不过还是谢谢。
  • df['metric_type'] = [x.strip() for x in df.metric_type] 怎么样?

标签: python pandas apply


【解决方案1】:

使用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 秒

【讨论】:

  • np,为我的新问题和答案投票。我通过手机应用发布了这个问题。
  • 对这个问题的支持是我所需要的。希望 numpy 的人会喜欢它
【解决方案2】:

这是一个比@piRSquared 快约 20% 的替代方案,并给出了相同的答案。我不会建议它更好或更坏(一般而言),但赏金是在接受该答案后发布的,因此我将提供此选项作为附加选项。

%%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 和 2/3 来自使用 pivot 而不是 unstack。 (concat 步骤是相同的​​,而且速度非常快)。

【讨论】:

    【解决方案3】:

    查看最终数据帧的创建方式,与目前提到的其他方法相比,字符串列的单热编码在整体性能方面似乎并不是一个坏主意。

    步骤:

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

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

    3. 对获得的这些列进行排序,以便它们按字典顺序排序,并且重复的列(如果存在)将彼此相邻放置。允许根据这些列组合修改DF
    4. 使用np.uniquereturn_index=True 参数来提取存在的唯一列及其对应的索引。
    5. 我们需要找到一种方法将相同的列组合成一个完整的列。为此,我们可以使用np.add.reduceat,它的工作方式类似于groupby 操作(等效-df.groupby(df.columns.tolist(), axis=1).sum()),但它的特点是速度非常快。要配对的索引由np.uniqueidx 提供。这些值的减少发生在这些切片上,并且它们的运行总和跨列计算(axis=1)。
    6. 返回的dtypebool,这有助于我们使用np.where,因为它的功能类似于布尔掩码,其中1/0 分别映射到True/False。然后,这些 1 由 metric_value 系列中的值填充,而 0 由 NaN 填充。
    7. 我们的DF 现已准备就绪,需要将其与原始的起始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)
    

    对于包含几千行的DF,与 OP 的想法相当:

    df1 = pd.concat([df] * 30000, ignore_index=True)
    df1.shape
    (90000, 2)
    
    # Check whether they produce the same outcome
    dummies_strip_concat(df1).equals(pir(df1))
    True
    
    %timeit pir(df1)
    10 loops, best of 3: 97.5 ms per loop
    
    %timeit johne(df1)
    10 loops, best of 3: 76.5 ms per loop
    
    %timeit dummies_strip_concat(df1)
    100 loops, best of 3: 13.2 ms per loop
    

    【讨论】:

    • 非常好的解决方案。
    • 谢谢!我花了一段时间才达到这一点。也许你可以想出一些你新学到的numpy 技术和更好的我的技术。有一个美好的一年!
    • 我只是喜欢让人们参与进来并学习其他人的技术。也祝你新年快乐。
    • 第 2 步非常聪明。遵循相同的逻辑,在应用 get_dummies 之前将metric_type 转换为分类是否有任何收益或损失?只是好奇有没有影响。谢谢。
    • @NickilMaveli tyvm
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-21
    • 2019-12-22
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 2023-02-02
    相关资源
    最近更新 更多