【问题标题】:Combine two columns of text in pandas dataframe在熊猫数据框中合并两列文本
【发布时间】:2013-10-23 01:34:10
【问题描述】:

我在 Python 中有一个使用 pandas 的 20 x 4000 数据框。其中两列名为Yearquarter。我想创建一个名为period 的变量,它使Year = 2000quarter= q2 变为2000q2

有人可以帮忙吗?

【问题讨论】:

    标签: python pandas dataframe


    【解决方案1】:

    类似于@geher 的答案,但可以使用您喜欢的任何分隔符:

    SEP = " "
    INPUT_COLUMNS_WITH_SEP = ",sep,".join(INPUT_COLUMNS).split(",")
    
    df.assign(sep=SEP)[INPUT_COLUMNS_WITH_SEP].sum(axis=1)
    

    【讨论】:

      【解决方案2】:

      如果两列都是字符串,可以直接拼接:

      df["period"] = df["Year"] + df["quarter"]
      

      如果其中一列(或两列)不是字符串类型,则应先转换(它们),

      df["period"] = df["Year"].astype(str) + df["quarter"]
      

      这样做时要小心 NaN!


      如果需要连接多个字符串列,可以使用agg

      df['period'] = df[['Year', 'quarter', ...]].agg('-'.join, axis=1)
      

      其中“-”是分隔符。

      【讨论】:

      • 是否可以在不输入所有列的情况下将多个列添加在一起?比如add(dataframe.iloc[:, 0:10])
      • @Heisenberg 这应该可以通过 Python 内置 sum 实现。
      • @silvado 你能举个例子来添加多列吗?谢谢
      • 请注意,您首先需要将 map(str) 应用于所有不是字符串的列。如果季度是一个数字,你会这样做dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str) map 只是将字符串转换应用于所有条目。
      • 如果你有 nan 值,这个解决方案可能会产生问题,请小心
      【解决方案3】:

      你可以使用 lambda:

      combine_lambda = lambda x: '{}{}'.format(x.Year, x.quarter)
      

      然后使用它来创建新列:

      df['period'] = df.apply(combine_lambda, axis = 1)
      

      【讨论】:

        【解决方案4】:

        小数据集(
        [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]
        

        或稍慢但更紧凑:

        df.Year.str.cat(df.quarter)
        

        更大的数据集(> 150 行)

        df['Year'].astype(str) + df['quarter']
        

        更新:时序图 Pandas 0.23.4

        让我们在 200K 行 DF 上测试它:

        In [250]: df
        Out[250]:
           Year quarter
        0  2014      q1
        1  2015      q2
        
        In [251]: df = pd.concat([df] * 10**5)
        
        In [252]: df.shape
        Out[252]: (200000, 2)
        

        更新:使用 Pandas 0.19.0 的新时间

        时序没有 CPU/GPU 优化(从最快到最慢排序):

        In [107]: %timeit df['Year'].astype(str) + df['quarter']
        10 loops, best of 3: 131 ms per loop
        
        In [106]: %timeit df['Year'].map(str) + df['quarter']
        10 loops, best of 3: 161 ms per loop
        
        In [108]: %timeit df.Year.str.cat(df.quarter)
        10 loops, best of 3: 189 ms per loop
        
        In [109]: %timeit df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)
        1 loop, best of 3: 567 ms per loop
        
        In [110]: %timeit df[['Year','quarter']].astype(str).sum(axis=1)
        1 loop, best of 3: 584 ms per loop
        
        In [111]: %timeit df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
        1 loop, best of 3: 24.7 s per loop
        

        时序使用 CPU/GPU 优化:

        In [113]: %timeit df['Year'].astype(str) + df['quarter']
        10 loops, best of 3: 53.3 ms per loop
        
        In [114]: %timeit df['Year'].map(str) + df['quarter']
        10 loops, best of 3: 65.5 ms per loop
        
        In [115]: %timeit df.Year.str.cat(df.quarter)
        10 loops, best of 3: 79.9 ms per loop
        
        In [116]: %timeit df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)
        1 loop, best of 3: 230 ms per loop
        
        In [117]: %timeit df[['Year','quarter']].astype(str).sum(axis=1)
        1 loop, best of 3: 230 ms per loop
        
        In [118]: %timeit df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
        1 loop, best of 3: 9.38 s per loop
        

        回复@anton-vbr的投稿

        【讨论】:

        • 261和264在你的时间上有什么区别?
        • @AntonProtopopov 显然是 100 毫秒 :)
        • @AntonProtopopov,我猜这是两种时间的混合——一个使用了 CPU/GPU 优化,另一个没有。我已经更新了我的答案并将两个时间设置都放在那里...
        • 如果所有列看起来都可能是整数(即整数的字符串形式),则使用 .sum() 会失败。相反,熊猫似乎在求和之前将它们转换回数字!
        • @MaxU 你是如何进行 CPU/GPU 优化的?这只是一台更强大的计算机还是你用代码做的?
        【解决方案5】:

        我的看法....

        listofcols = ['col1','col2','col3']
        df['combined_cols'] = ''
        
        for column in listofcols:
            df['combined_cols'] = df['combined_cols'] + ' ' + df[column]
        '''
        

        【讨论】:

        • 您应该在此代码 sn-p 中添加解释。仅添加代码答案会鼓励人们使用他们不理解且无助于学习的代码。
        【解决方案6】:

        推广到多列,为什么不呢:

        columns = ['whatever', 'columns', 'you', 'choose']
        df['period'] = df[columns].astype(str).sum(axis=1)
        

        【讨论】:

        • 看起来很酷,但是如果我想在字符串之间添加一个分隔符,比如“-”呢?
        • @Odisseo 可能会创建一个分隔符列?
        【解决方案7】:

        这是我对上述解决方案的总结,用于将具有 int 和 str 值的两列连接/组合成一个新列,在列的值之间使用分隔符。三种解决方案可用于此目的。

        # be cautious about the separator, some symbols may cause "SyntaxError: EOL while scanning string literal".
        # e.g. ";;" as separator would raise the SyntaxError
        
        separator = "&&" 
        
        # pd.Series.str.cat() method does not work to concatenate / combine two columns with int value and str value. This would raise "AttributeError: Can only use .cat accessor with a 'category' dtype"
        
        df["period"] = df["Year"].map(str) + separator + df["quarter"]
        df["period"] = df[['Year','quarter']].apply(lambda x : '{} && {}'.format(x[0],x[1]), axis=1)
        df["period"] = df.apply(lambda x: f'{x["Year"]} && {x["quarter"]}', axis=1)
        

        【讨论】:

          【解决方案8】:

          此解决方案使用中间步骤将 DataFrame 的两列压缩为包含值列表的单列。 这不仅适用于字符串,也适用于所有类型的 column-dtypes

          import pandas as pd
          df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
          df['list']=df[['Year','quarter']].values.tolist()
          df['period']=df['list'].apply(''.join)
          print(df)
          

          结果:

             Year quarter        list  period
          0  2014      q1  [2014, q1]  2014q1
          1  2015      q2  [2015, q2]  2015q2
          

          【讨论】:

          • 看起来其他 dtype 不起作用。我得到一个 TypeError: sequence item 1: expected str instance, float found
          • 首先对字符串应用强制转换。连接操作仅适用于字符串
          • 此解决方案无法将具有不同 dtype 的两列组合在一起,请参阅我的答案以获取针对这种情况的正确解决方案。
          • 为什么不使用.apply(''.join)而不是.str.join('')
          【解决方案9】:

          使用zip 可能会更快:

          df["period"] = [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]
          

          图表:

          import pandas as pd
          import numpy as np
          import timeit
          import matplotlib.pyplot as plt
          from collections import defaultdict
          
          df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
          
          myfuncs = {
          "df['Year'].astype(str) + df['quarter']":
              lambda: df['Year'].astype(str) + df['quarter'],
          "df['Year'].map(str) + df['quarter']":
              lambda: df['Year'].map(str) + df['quarter'],
          "df.Year.str.cat(df.quarter)":
              lambda: df.Year.str.cat(df.quarter),
          "df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)":
              lambda: df.loc[:, ['Year','quarter']].astype(str).sum(axis=1),
          "df[['Year','quarter']].astype(str).sum(axis=1)":
              lambda: df[['Year','quarter']].astype(str).sum(axis=1),
              "df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)":
              lambda: df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1),
              "[''.join(i) for i in zip(dataframe['Year'].map(str),dataframe['quarter'])]":
              lambda: [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]
          }
          
          d = defaultdict(dict)
          step = 10
          cont = True
          while cont:
              lendf = len(df); print(lendf)
              for k,v in myfuncs.items():
                  iters = 1
                  t = 0
                  while t < 0.2:
                      ts = timeit.repeat(v, number=iters, repeat=3)
                      t = min(ts)
                      iters *= 10
                  d[k][lendf] = t/iters
                  if t > 2: cont = False
              df = pd.concat([df]*step)
          
          pd.DataFrame(d).plot().legend(loc='upper center', bbox_to_anchor=(0.5, -0.15))
          plt.yscale('log'); plt.xscale('log'); plt.ylabel('seconds'); plt.xlabel('df rows')
          plt.show()
          

          【讨论】:

            【解决方案10】:

            让我们假设您的 dataframedf,列有 YearQuarter

            import pandas as pd
            df = pd.DataFrame({'Quarter':'q1 q2 q3 q4'.split(), 'Year':'2000'})
            

            假设我们要查看数据框;

            df
            >>>  Quarter    Year
               0    q1      2000
               1    q2      2000
               2    q3      2000
               3    q4      2000
            

            最后,将 YearQuarter 连接起来,如下所示。

            df['Period'] = df['Year'] + ' ' + df['Quarter']
            

            您现在可以print df 查看生成的数据框。

            df
            >>>  Quarter    Year    Period
                0   q1      2000    2000 q1
                1   q2      2000    2000 q2
                2   q3      2000    2000 q3
                3   q4      2000    2000 q4
            

            如果您不想要年份和季度之间的空间,只需将其删除即可;

            df['Period'] = df['Year'] + df['Quarter']
            

            【讨论】:

            • 指定为字符串df['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
            • 当我运行df2['filename'] = df2['job_number'] + '.' + df2['task_number']df2['filename'] = df2['job_number'].map(str) + '.' + df2['task_number'].map(str) 时,我得到TypeError: Series cannot perform the operation +
            • 但是,df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str) 确实有效。
            • @KarlBaker,我认为您的输入中没有字符串。但我很高兴你明白了这一点。如果您查看我在上面创建的示例dataframe,您会看到所有列都是strings。
            • 这个解决方案到底有什么意义,因为它与最佳答案相同?
            【解决方案11】:

            可以使用DataFrameassign方法:

            df= (pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']}).
              assign(period=lambda x: x.Year+x.quarter ))
            

            【讨论】:

              【解决方案12】:

              cat() of the .str accessor 方法对此非常有效:

              >>> import pandas as pd
              >>> df = pd.DataFrame([["2014", "q1"], 
              ...                    ["2015", "q3"]],
              ...                   columns=('Year', 'Quarter'))
              >>> print(df)
                 Year Quarter
              0  2014      q1
              1  2015      q3
              >>> df['Period'] = df.Year.str.cat(df.Quarter)
              >>> print(df)
                 Year Quarter  Period
              0  2014      q1  2014q1
              1  2015      q3  2015q3
              

              cat() 甚至允许您添加分隔符,例如,假设您只有整数表示年份和期间,您可以这样做:

              >>> import pandas as pd
              >>> df = pd.DataFrame([[2014, 1],
              ...                    [2015, 3]],
              ...                   columns=('Year', 'Quarter'))
              >>> print(df)
                 Year Quarter
              0  2014       1
              1  2015       3
              >>> df['Period'] = df.Year.astype(str).str.cat(df.Quarter.astype(str), sep='q')
              >>> print(df)
                 Year Quarter  Period
              0  2014       1  2014q1
              1  2015       3  2015q3
              

              加入多列只需将系列列表或包含除第一列以外的所有列的数据框作为参数传递给在第一列(系列)上调用的str.cat()

              >>> df = pd.DataFrame(
              ...     [['USA', 'Nevada', 'Las Vegas'],
              ...      ['Brazil', 'Pernambuco', 'Recife']],
              ...     columns=['Country', 'State', 'City'],
              ... )
              >>> df['AllTogether'] = df['Country'].str.cat(df[['State', 'City']], sep=' - ')
              >>> print(df)
                Country       State       City                   AllTogether
              0     USA      Nevada  Las Vegas      USA - Nevada - Las Vegas
              1  Brazil  Pernambuco     Recife  Brazil - Pernambuco - Recife
              

              请注意,如果您的 pandas 数据框/系列有空值,则需要包含参数 na_rep 以将 NaN 值替换为字符串,否则合并列将默认为 NaN。

              【讨论】:

              • 这似乎比lambdamap 更好(也可能更有效);而且它读起来最干净。
              • @ZakS,将剩余的列作为数据框而不是系列作为第一个参数传递给str.cat()。我会修改答案
              • 您使用的是哪个版本的熊猫?我得到 ValueError: 你的意思是提供一个 sep 关键字吗?在熊猫-0.23.4 中。谢谢!
              • @QinqingLiu,我用 pandas-0.23.4 重新测试了这些,它们似乎有效。 sep 参数仅在您打算分隔连接字符串的各个部分时才是必需的。如果您遇到错误,请向我们展示您的失败示例。
              • @arun-menon:我不明白为什么不这样做。例如,在上面的最后一个示例中,您可以使用.str.cat(df[['State', 'City']], sep ='\n')。不过,我还没有测试过。
              【解决方案13】:

              使用.combine_first

              df['Period'] = df['Year'].combine_first(df['Quarter'])
              

              【讨论】:

              • 这是不正确的。 .combine_first 将导致来自'Year' 的值存储在'Period' 中,或者,如果它为 Null,则来自'Quarter' 的值。它不会连接两个字符串并将它们存储在'Period'
              【解决方案14】:

              效率更高

              def concat_df_str1(df):
                  """ run time: 1.3416s """
                  return pd.Series([''.join(row.astype(str)) for row in df.values], index=df.index)
              

              这是一个时间测试:

              import numpy as np
              import pandas as pd
              
              from time import time
              
              
              def concat_df_str1(df):
                  """ run time: 1.3416s """
                  return pd.Series([''.join(row.astype(str)) for row in df.values], index=df.index)
              
              
              def concat_df_str2(df):
                  """ run time: 5.2758s """
                  return df.astype(str).sum(axis=1)
              
              
              def concat_df_str3(df):
                  """ run time: 5.0076s """
                  df = df.astype(str)
                  return df[0] + df[1] + df[2] + df[3] + df[4] + \
                         df[5] + df[6] + df[7] + df[8] + df[9]
              
              
              def concat_df_str4(df):
                  """ run time: 7.8624s """
                  return df.astype(str).apply(lambda x: ''.join(x), axis=1)
              
              
              def main():
                  df = pd.DataFrame(np.zeros(1000000).reshape(100000, 10))
                  df = df.astype(int)
              
                  time1 = time()
                  df_en = concat_df_str4(df)
                  print('run time: %.4fs' % (time() - time1))
                  print(df_en.head(10))
              
              
              if __name__ == '__main__':
                  main()
              

              final,当使用sum(concat_df_str2)时,结果不是简单的concat,而是转成整数。

              【讨论】:

              • +1 简洁的解决方案,这也允许我们指定列:例如df.values[:, 0:3]df.values[:, [0,2]].
              【解决方案15】:
              df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
              df['period'] = df[['Year', 'quarter']].apply(lambda x: ''.join(x), axis=1)
              

              产生这个数据框

                 Year quarter  period
              0  2014      q1  2014q1
              1  2015      q2  2015q2
              

              此方法通过将 df[['Year', 'quarter']] 替换为数据帧的任何列切片来推广到任意数量的字符串列,例如df.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1).

              你可以查看更多关于apply()方法here的信息

              【讨论】:

              • lambda x: ''.join(x) 只是''.join,不是吗?
              • @OzgurOzturk:关键是 lambda x: ''.join(x) 构造的 lambda 部分没有做任何事情;这就像使用lambda x: sum(x) 而不是仅仅使用sum
              • 确认使用''.join时的结果相同,即:df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1)
              • @Archie join 在可迭代对象中只接受 str 实例。使用map 将它们全部转换为str,然后使用join
              • '-'.join(x.map(str))
              【解决方案16】:

              正如许多人之前提到的,您必须将每一列转换为字符串,然后使用加号运算符组合两个字符串列。使用 NumPy 可以获得很大的性能提升。

              %timeit df['Year'].values.astype(str) + df.quarter
              71.1 ms ± 3.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
              
              %timeit df['Year'].astype(str) + df['quarter']
              565 ms ± 22.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
              

              【讨论】:

              • 我想使用 numpyified 版本,但出现错误:输入df2['filename'] = df2['job_number'].values.astype(str) + '.' + df2['task_number'].values.astype(str) --> 输出:@ 987654323@。 job_number 和 task_number 都是整数。
              • 那是因为你正在组合两个 numpy 数组。如果将 numpy 数组与 pandas Series 结合使用,它会起作用。作为df['Year'].values.astype(str) + df.quarter
              【解决方案17】:
              def madd(x):
                  """Performs element-wise string concatenation with multiple input arrays.
              
                  Args:
                      x: iterable of np.array.
              
                  Returns: np.array.
                  """
                  for i, arr in enumerate(x):
                      if type(arr.item(0)) is not str:
                          x[i] = x[i].astype(str)
                  return reduce(np.core.defchararray.add, x)
              

              例如:

              data = list(zip([2000]*4, ['q1', 'q2', 'q3', 'q4']))
              df = pd.DataFrame(data=data, columns=['Year', 'quarter'])
              df['period'] = madd([df[col].values for col in ['Year', 'quarter']])
              
              df
              
                  Year    quarter period
              0   2000    q1  2000q1
              1   2000    q2  2000q2
              2   2000    q3  2000q3
              3   2000    q4  2000q4
              

              【讨论】:

                【解决方案18】:

                这是一个我觉得非常通用的实现:

                In [1]: import pandas as pd 
                
                In [2]: df = pd.DataFrame([[0, 'the', 'quick', 'brown'],
                   ...:                    [1, 'fox', 'jumps', 'over'], 
                   ...:                    [2, 'the', 'lazy', 'dog']],
                   ...:                   columns=['c0', 'c1', 'c2', 'c3'])
                
                In [3]: def str_join(df, sep, *cols):
                   ...:     from functools import reduce
                   ...:     return reduce(lambda x, y: x.astype(str).str.cat(y.astype(str), sep=sep), 
                   ...:                   [df[col] for col in cols])
                   ...: 
                
                In [4]: df['cat'] = str_join(df, '-', 'c0', 'c1', 'c2', 'c3')
                
                In [5]: df
                Out[5]: 
                   c0   c1     c2     c3                cat
                0   0  the  quick  brown  0-the-quick-brown
                1   1  fox  jumps   over   1-fox-jumps-over
                2   2  the   lazy    dog     2-the-lazy-dog
                

                【讨论】:

                • 仅供参考:此方法适用于 Python 3,但在 Python 2 中给我带来了麻烦。
                【解决方案19】:

                这次使用带有 string.format() 的 Lamba 函数。

                import pandas as pd
                df = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': ['q1', 'q2']})
                print df
                df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
                print df
                
                  Quarter  Year
                0      q1  2014
                1      q2  2015
                  Quarter  Year YearQuarter
                0      q1  2014      2014q1
                1      q2  2015      2015q2
                

                这允许您使用非字符串并根据需要重新格式化值。

                import pandas as pd
                df = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': [1, 2]})
                print df.dtypes
                print df
                
                df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}q{}'.format(x[0],x[1]), axis=1)
                print df
                
                Quarter     int64
                Year       object
                dtype: object
                   Quarter  Year
                0        1  2014
                1        2  2015
                   Quarter  Year YearQuarter
                0        1  2014      2014q1
                1        2  2015      2015q2
                

                【讨论】:

                • 更快:.apply(''.join(x), axis=1)
                【解决方案20】:

                虽然@silvado 的答案很好,但如果您将df.map(str) 更改为df.astype(str),它会更快:

                import pandas as pd
                df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
                
                In [131]: %timeit df["Year"].map(str)
                10000 loops, best of 3: 132 us per loop
                
                In [132]: %timeit df["Year"].astype(str)
                10000 loops, best of 3: 82.2 us per loop
                

                【讨论】:

                  猜你喜欢
                  • 2017-06-11
                  • 2016-01-01
                  相关资源
                  最近更新 更多