【问题标题】:Enumerate columns with same prefix枚举具有相同前缀的列
【发布时间】:2019-11-12 08:29:22
【问题描述】:

假设我们有以下简化数据:

df = pd.DataFrame({'A':list('abcd'),
                   'B':list('efgh'),
                   'Data_mean':[1,2,3,4],
                   'Data_std':[5,6,7,8],
                   'Data_corr':[9,10,11,12],
                   'Text_one':['foo', 'bar', 'foobar', 'barfoo'],
                   'Text_two':['bar', 'foo', 'barfoo', 'foobar'],
                   'Text_three':['bar', 'bar', 'barbar', 'foofoo']})

   A  B  Data_mean  Data_std  Data_corr Text_one Text_two Text_three
0  a  e          1         5          9      foo      bar        bar
1  b  f          2         6         10      bar      foo        bar
2  c  g          3         7         11   foobar   barfoo     barbar
3  d  h          4         8         12   barfoo   foobar     foofoo

我想枚举具有相同前缀的列。在这种情况下,前缀是Data, Text。所以预期输出将是:

   A  B  Data_mean1  Data_std2  Data_corr3 Text_one1 Text_two2 Text_three3
0  a  e           1          5           9       foo       bar         bar
1  b  f           2          6          10       bar       foo         bar
2  c  g           3          7          11    foobar    barfoo      barbar
3  d  h           4          8          12    barfoo    foobar      foofoo

注意枚举列。


尝试的解决方案 #1

def enumerate_cols(dataframe, prefix):
    cols = []
    num = 1
    for col in dataframe.columns:
        if col.startswith(prefix):
            cols.append(col + str(num))
            num += 1
        else:
            cols.append(col)

    return cols
enumerate_cols(df, 'Data')

['A',
 'B',
 'Data_mean1',
 'Data_std2',
 'Data_corr3',
 'Text_one',
 'Text_two',
 'Text_three']

尝试的解决方案 #2:

[c+str(x+1) for x, c in enumerate([col for col in df.columns if col.startswith('Data')])]
['Data_mean1', 'Data_std2', 'Data_corr3']

问题:有没有更简单的解决方案,我也查看了df.filter(like='Data') 等。但这看起来也很牵强。


XY 问题
请确保我没有落入XY problem。我想使用pd.wide_to_long,但是stubnames 列需要以数字为后缀才能融合数据框。

引用自文档:

对于存根名称 [‘A’, ‘B’],此函数期望找到一组或多组格式为 A-suffix1, A-suffix2,..., B-suffix1, B-suffix2, 的列

pd.wide_to_long(df, stubnames=['Data', 'Text'], i=['A', 'B'], j='grp', sep='_')

这会返回一个空数据框。

【问题讨论】:

  • 您的问题是仅限于两个前缀“数据”和“文本”还是应该概括?另外,A 和 B 应该是索引吗?
  • Q1:是的,前缀的数量会很少(主要是 2 或 3 个),因此不必一概而论。 Q2:如果你愿意,你可以将它们设置为索引,现在没关系。 @cs95
  • 请问一个问题,从数据结构层面,最后加1到n是多余的,为什么要这样做
  • 当然@WeNYoBen,但我不确定我是否理解正确,你指的是代码的哪一部分?
  • 我的意思是这个问题,只是好奇为什么要在末尾添加数字计数

标签: python pandas dataframe


【解决方案1】:

想法是将具有相同前缀的列分组,并为它们建立一个 cumcount。

由于我们需要单独处理没有前缀的列,我们需要使用GroupBy.cumcountnp.where 分两步完成:

cols = df.columns.str.split('_').str[0].to_series()

df.columns = np.where(
    cols.groupby(level=0).transform('count') > 1, 
    cols.groupby(level=0).cumcount().add(1).astype(str).radd(df.columns), 
    cols
)

df
   A  B  Data_mean1  Data_std2  Data_corr3 Text_one1 Text_two2 Text_three3
0  a  e           1          5           9       foo       bar         bar
1  b  f           2          6          10       bar       foo         bar
2  c  g           3          7          11    foobar    barfoo      barbar
3  d  h           4          8          12    barfoo    foobar      foofoo

更简单的解决方案是将不想添加后缀的列设置为索引。然后你可以简单地做

df.set_index(['A', 'B'], inplace=True)
df.columns = (
    df.columns.str.split('_')
      .str[0]
      .to_series()
      .groupby(level=0)
      .cumcount()
      .add(1)
      .astype(str)
      .radd(df.columns))

df
     Data_mean1  Data_std2  Data_corr3 Text_one1 Text_two2 Text_three3
A B                                                                   
a e           1          5           9       foo       bar         bar
b f           2          6          10       bar       foo         bar
c g           3          7          11    foobar    barfoo      barbar
d h           4          8          12    barfoo    foobar      foofoo

【讨论】:

  • 完美!很好地使用了groupby,甚至没有来找我。 Plus 不知道 pandas 有元素运算符 (radd)。谢谢!
【解决方案2】:

您还可以使用 defaultdict 为每个前缀创建一个计数器。

from collections import defaultdict

prefix_starting_location = 2
columns = df.columns[prefix_starting_location:]
prefixes = set(col.split('_')[0] for col in columns)

new_cols = []
dd = defaultdict(int)
for col in columns:
    prefix = col.split('_')[0]
    dd[prefix] += 1
    new_cols.append(col + str(dd[prefix]))
df.columns = df.columns[:prefix_starting_location].tolist() + new_cols
>>> df
   A  B  Data_mean1  Data_std2  Data_corr3 Text_one1 Text_two2 Text_three3
0  a  e           1          5           9       foo       bar         bar
1  b  f           2          6          10       bar       foo         bar
2  c  g           3          7          11    foobar    barfoo      barbar
3  d  h           4          8          12    barfoo    foobar      foofoo
​

如果前缀已知:

prefixes = ['Data', 'Text']
new_cols = []
dd = defaultdict(int)
for col in df.columns:
    prefix = col.split('_')[0]
    if prefix in prefixes:
        dd[prefix] += 1
        new_cols.append(col + str(dd[prefix]))
    else:
        new_cols.append(col)

如果您的拆分字符 _ 不在您的任何数据字段中:

new_cols = []
dd = defaultdict(int)
for col in df.columns:
    if '_' in col:
        prefix = col.split('_')[0]
        dd[prefix] += 1
        new_cols.append(col + str(dd[prefix]))
    else:
        new_cols.append(col)

df.columns = new_cols

【讨论】:

  • 谢谢,这在这种情况下也有效,赞成。唯一的问题是我不知道 prefix columns 下次从哪里开始。另外,我担心它们将来可能会混在一起(尽管机会非常低),这就是为什么我没有在我的问题中提到这一点。
  • 问题是如何区分数据列和前缀列。您要么需要知道前缀名称,要么确保拆分字符 (_) 不包含在任何数据列中,例如some_data.
  • 是的,我同意,关键是我依赖于 IT 部门。我已经向他们发送了提取指令,直到现在一切正常。只是努力确保未来的安全。
【解决方案3】:

你可以使用rename 如:

l_word = ['Data','Text']
df = df.rename(columns={ col:col+str(i+1) 
                         for word in l_word 
                         for i, col in enumerate(df.filter(like=word))})

【讨论】:

    【解决方案4】:

    根据我们的谈话,方法melt

    s=df.melt(['A','B']).assign(x=lambda x : x.groupby(x.variable.str.split('_').str[0]).cumcount(),y=lambda x : x.variable.str.split('_').str[0]) 
    
    # after this the problem became a pivot problem 
    pd.crosstab([s.A,s.B,s.x],columns=s.y,values=s.value,aggfunc='sum')
    y      Data    Text
    A B x              
    a e 0     1     foo
        4     5     bar
        8     9     bar
    b f 1     2     bar
        5     6     foo
        9    10     bar
    c g 2     3  foobar
        6     7  barfoo
        10   11  barbar
    d h 3     4  barfoo
        7     8  foobar
        11   12  foofoo
    

    【讨论】:

    • 谢谢,对于那些需要更“灵活”的熔体的人来说,这是要走的路。
    猜你喜欢
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-19
    • 2022-01-20
    • 1970-01-01
    • 2022-12-16
    相关资源
    最近更新 更多