【问题标题】:Is there a fast way to convert a Pandas dataframe of columns to a list of strings?有没有一种快速的方法可以将 Pandas 的列数据框转换为字符串列表?
【发布时间】:2020-01-11 11:40:29
【问题描述】:

这与大多数人在列表和数据框之间转换时想做的事情有些相反。

我希望将大型数据帧(10M+ 行,20+ 列)转换为字符串列表,其中每个条目都是数据帧中每一行的字符串表示形式。我可以使用 pandas 的 to_csv() 方法来做到这一点,但我想知道是否有更快的方法,因为这被证明是我代码中的瓶颈。

最小工作示例:

import numpy as np
import pandas as pd

# Create the initial dataframe.
size = 10000000
cols = list('abcdefghijklmnopqrstuvwxyz')
df = pd.DataFrame()
for col in cols:
    df[col] = np.arange(size)
    df[col] = "%s_" % col + df[col].astype(str)

# Convert to the required list structure
ret_val = _df_.to_csv(index=False, header=False).split("\n")[:-1]

对于我的 Core i9 的单个线程上的 10,000,000 行数据帧,上述代码的转换方面大约需要 90 秒,并且高度依赖于 CPU。如果可能的话,我希望将其降低一个数量级。

编辑:我不希望将数据保存到 .csv 或文件中。我只是想将数据框转换为字符串数组。

编辑: 只有 5 列的输入/输出示例:

In  [1]: df.head(10)
Out [1]:    a       b       c       d       e
         0  a_0     b_0     c_0     d_0     e_0
         1  a_1     b_1     c_1     d_1     e_1
         2  a_2     b_2     c_2     d_2     e_2
         3  a_3     b_3     c_3     d_3     e_3
         4  a_4     b_4     c_4     d_4     e_4
         5  a_5     b_5     c_5     d_5     e_5
         6  a_6     b_6     c_6     d_6     e_6
         7  a_7     b_7     c_7     d_7     e_7
         8  a_8     b_8     c_8     d_8     e_8
         9  a_9     b_9     c_9     d_9     e_9

In  [2]: ret_val[:10]
Out [2]: ['a_0,b_0,c_0,d_0,e_0',
          'a_1,b_1,c_1,d_1,e_1',
          'a_2,b_2,c_2,d_2,e_2',
          'a_3,b_3,c_3,d_3,e_3',
          'a_4,b_4,c_4,d_4,e_4',
          'a_5,b_5,c_5,d_5,e_5',
          'a_6,b_6,c_6,d_6,e_6',
          'a_7,b_7,c_7,d_7,e_7',
          'a_8,b_8,c_8,d_8,e_8',
          'a_9,b_9,c_9,d_9,e_9']

【问题讨论】:

  • 您为什么要这样做?我会尽我最大的努力将大部分数据保留在 RAM 之外,当然会被解析为适当的数据类型,以便可以有效地对其进行操作
  • 在两个字符串列表之间进行熵和信息比较时需要它。

标签: python python-3.x pandas csv


【解决方案1】:

multiprocessing 的速度提高了约 2.5 倍...

import multiprocessing

# df from OPs above code available in global scope

def fn(i):
    return df[i:i+1000].to_csv(index=False, header=False).split('\n')[:-1]

with multiprocessing.Pool() as pool:
    result = []
    for a in pool.map(fn, range(0, len(df), 1000)):
        result.extend(a)

在我的笔记本电脑上将 100 万行的总时间从 6.8 秒减少到 2.8 秒,因此有望扩展到 i9 CPU 中的更多内核。

这取决于 Unix fork 语义来与子进程共享数据帧,显然做了更多的工作,但可能会有所帮助......

使用来自 Massifox 的 numpy.savetxt 建议和 multiprocessing 可以将此时间缩短到 2.0 秒,只需 map 以下函数:

def fn2(i):
    with StringIO() as fd:
        np.savetxt(fd, df[i:i+N], fmt='%s', delimiter=',')
        return fd.getvalue().split('\n')[:-1]

结果其他方面基本相同

您说“数据框是类中的变量”的评论可以通过多种不同的方式进行修复。一种简单的方法是将数据帧传递给Pool initializer,此时它不会被选择(无论如何在Unix下)并将对它的引用存储在某个全局变量中。然后每个工作进程都可以使用此引用,例如:

def stash_df(df):
    global the_df
    the_df = df

def fn(i):
    with StringIO() as fd:
        np.savetxt(fd, the_df[i:i+N], fmt='%s', delimiter=',')
        return fd.getvalue().split('\n')[:-1]

with multiprocessing.Pool(initializer=stash_df, initargs=(df,)) as pool:
    result = []
    for a in pool.map(fn, range(0, len(df), N)):
        result.extend(a)

只要每个 Pool 都被单个数据帧使用,这就可以了

【讨论】:

  • 由于某种原因,多处理实现在规模上要慢得多。我怀疑这是由于父进程和不同子进程之间的大型数据帧块的管道。但是,numpy.savetext 方法提供了近 2 倍的加速,非常感谢!
  • 假设你可以依赖分叉;唯一可观的 IPC 应该是从子进程到父进程返回 pickled 字符串列表。您想要安排一些事情,以便在生成池后数据框最终在子进程中可用(即不作为参数传递给 mapped 函数)
  • 问题是数据框是类中的变量。如果要并行化的方法是类的一部分,它可以看到数据框实例但不能被腌制。为了腌制方法,它不能是类实例,因此如果没有明确传递给它,就无法看到数据框...
【解决方案2】:

您可以尝试不同的方法来加快数据写入磁盘的速度:

  1. 写入压缩文件可以将写入速度提高 10 倍

    df.to_csv('output.csv.gz' , header=True , index=False , chunksize=100000 , compression='gzip' , encoding='utf-8')
    选择最适合您的块大小。

  2. 切换到hdf格式:

    df.to_hdf(r'output.h5', mode='w')

  3. 根据krassowski answer,使用numpy。例如,使用以下 df:

    df=pd.DataFrame({'A':range(1000000)}) df['B'] = df.A + 1.0 df['C'] = df.A + 2.0 df['D'] = df.A + 3.0

    熊猫到 csv:

    df.to_csv('pandas_to_csv', index=False)
    在我的计算机上,每个循环需要 6.45 秒 ± 1.05 秒(平均值 ± 标准偏差,7 次运行,每个循环 1 个)`

    numpy 到 csv:

    savetxt( 'numpy_savetxt', aa.values, fmt='%d,%.1f,%.1f,%.1f', header=','.join(aa.columns), comments='')
    在我的计算机上,每个循环需要 3.38 秒 ± 224 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个)

  4. 使用Pandaral·lel
    是一个简单而高效的工具,可以在所有 CPU 上并行化 Pandas 计算(仅限 Linux 和 MacOS)。如何仅用一行代码显着加快 Pandas 计算速度。酷!

  5. 您可以考虑将 Pandas 数据框替换为 DASK 数据框。 CSV API 与熊猫非常相似。

【讨论】:

  • 您好,感谢您的输入,但我不想将数据保存到文件中。我只是想创建一个字符串列表。
【解决方案3】:

使用字典可以稍微提高性能:

size = 100000
cols = list('abcdefghijklmnopqrstuvwxyz')

字典版本:

%%timeit
dict_res= {}
for col in cols:
    dict_res[col] = ["%s_%d" % (col, n) for n in np.arange(size)]
df2 = pd.DataFrame(dict_res)
# 1.56 s ± 99 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

你的例子:

%%timeit
df = pd.DataFrame()
for col in cols:
    df[col] = np.arange(size)
    df[col] = "%s_" % col + df[col].astype(str)
# 1.91 s ± 84.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

多处理

使用多处理,代码如下:

import multiprocessing
import numpy as np
import pandas pd

size = 100000
cols = list('abcdefghijklmnopqrstuvwxyz')
n_core = muliprocessing.cpu_count()

def format_col(col):
    return col, ["%s_%d" % (col, n) for n in np.arange(size)]

with multiprocessing.Pool(n_core) as pool:
    result = {}
    for res in pool.map(format_col, cols):
        result[res[0]]=res[1]
        result.extend(res)
df = pd.DataFrame(result)

现在我无法在我的电脑上运行它。但性能可能会提高。

【讨论】:

  • 这加快了数据帧的生成,这不是问题/问题。我需要加快数据帧到字符串列表的转换。
【解决方案4】:

试试这个解决方案:

    list_of_string = df.head(5).set_index(cols[0]).to_string(header=False).split('\n')[1:]

     # output: 
['a_0  b_1  c_1  d_1  e_1  f_1  g_1  h_1  i_1  j_1  k_1  l_1  m_1  n_1  o_1  p_1  q_1  r_1  s_1  t_1  u_1  v_1  w_1  x_1  y_1  z_1',
     'a_1  b_2  c_2  d_2  e_2  f_2  g_2  h_2  i_2  j_2  k_2  l_2  m_2  n_2  o_2  p_2  q_2  r_2  s_2  t_2  u_2  v_2  w_2  x_2  y_2  z_2',
     'a_2  b_3  c_3  d_3  e_3  f_3  g_3  h_3  i_3  j_3  k_3  l_3  m_3  n_3  o_3  p_3  q_3  r_3  s_3  t_3  u_3  v_3  w_3  x_3  y_3  z_3',
     'a_3  b_4  c_4  d_4  e_4  f_4  g_4  h_4  i_4  j_4  k_4  l_4  m_4  n_4  o_4  p_4  q_4  r_4  s_4  t_4  u_4  v_4  w_4  x_4  y_4  z_4',
     'a_4  b_5  c_5  d_5  e_5  f_5  g_5  h_5  i_5  j_5  k_5  l_5  m_5  n_5  o_5  p_5  q_5  r_5  s_5  t_5  u_5  v_5  w_5  x_5  y_5  z_5']

如果你想用逗号替换空格:

[s.replace('  ', ',') for s in list_of_string]
# output:
['a_0,b_1,c_1,d_1,e_1,f_1,g_1,h_1,i_1,j_1,k_1,l_1,m_1,n_1,o_1,p_1,q_1,r_1,s_1,t_1,u_1,v_1,w_1,x_1,y_1,z_1',
 'a_1,b_2,c_2,d_2,e_2,f_2,g_2,h_2,i_2,j_2,k_2,l_2,m_2,n_2,o_2,p_2,q_2,r_2,s_2,t_2,u_2,v_2,w_2,x_2,y_2,z_2',
 'a_2,b_3,c_3,d_3,e_3,f_3,g_3,h_3,i_3,j_3,k_3,l_3,m_3,n_3,o_3,p_3,q_3,r_3,s_3,t_3,u_3,v_3,w_3,x_3,y_3,z_3',
 'a_3,b_4,c_4,d_4,e_4,f_4,g_4,h_4,i_4,j_4,k_4,l_4,m_4,n_4,o_4,p_4,q_4,r_4,s_4,t_4,u_4,v_4,w_4,x_4,y_4,z_4',
 'a_4,b_5,c_5,d_5,e_5,f_5,g_5,h_5,i_5,j_5,k_5,l_5,m_5,n_5,o_5,p_5,q_5,r_5,s_5,t_5,u_5,v_5,w_5,x_5,y_5,z_5']

您可以根据我在previous answers 中给您的建议加快此代码的速度。

提示:DASK、Pandaral·lel 和 multiprocessing 是你的朋友!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-20
    • 2016-01-31
    • 2018-03-02
    • 2020-04-23
    • 2021-12-13
    • 1970-01-01
    • 2017-05-03
    • 1970-01-01
    相关资源
    最近更新 更多