在从多个数据源创建DataFrame时,我们应该使用循环或列表理解吗?

use*_*881 3 python dataframe pandas

这个问题与@jpp:s在将具有类似名称约定的文件合并到数据框中的答案以及标记早期线程(将csv文件放在单独的数据帧中取决于文件名)作为重复的决定有关,因为该线程中的三个答案是要么不工作(2/3)要么不工作(1/3).

忽略那些无法解决的答案,一个答案(我的回答)据说质量很差,因为" concat在文档中明确不建议使用for循环".


批评的方法:

dataframes = {}
for filename in filenames:
    _df = pd.read_csv(filename)
    key = filename[:3]
    try:
       dataframes[key] = pd.concat([dataframes[key], _df], ignore_index=True)
    except KeyError:
       dataframes[key] = _df
Run Code Online (Sandbox Code Playgroud)

接受的方法(dd是一个字典,其中每个值是文件名列表,每个键是每个文件名的前三个字符):

dict_of_dfs
for k, v in dd.items():
    dict_of_dfs[k] = pd.concat([pd.read_csv(fn) for fn in v], ignore_index=True)
Run Code Online (Sandbox Code Playgroud)

现在我同意concat调用中的列表理解(接受的方法)比每个调用的for循环更有效.concatDataFrame

但这是否意味着我们应该总是DataFrames通过在concat调用(或append)中使用列表推导创建多个数据源,并且使用for循环是如此之以至于它实际上是错误的?可读性怎么样?我个人(当然)认为我批评的方法更具可读性.


如果我们阅读pandas文档,DataFrame.append我们就会知道for循环或列表理解都不是"推荐的生成DataFrames的方法":

以下虽然不推荐用于生成DataFrame的方法,但它显示了两种从多个数据源生成DataFrame的方法.

效率较低:

>>> df = pd.DataFrame(columns=['A'])
>>> for i in range(5):
...     df = df.append({'A': i}, ignore_index=True)
>>> df

   A
0  0
1  1
2  2
3  3
4  4
Run Code Online (Sandbox Code Playgroud)

更高效:

>>> pd.concat([pd.DataFrame([i], columns=['A']) for i in range(5)],
...           ignore_index=True)

   A
0  0
1  1
2  2
3  3
4  4
Run Code Online (Sandbox Code Playgroud)

所以.我的问题如下:

  1. 循环和使用concat多个数据源来创建一个或多个DataFrame如此差的实例,这是错误的吗?

  2. 在这种情况下我们是否应该始终使用列表理解?

  3. 文档似乎不建议既不使用列表推理也不使用循环,那么从多个数据源创建(s)的推荐方法是DataFrame什么?


我非常感谢你的回答@piRSquared和@jpp.我仍然不相信有关的明确的解雇concat在for循环为到被点错了,而列表理解是正确的接受的.

鉴于以下测试数据:

df = pd.DataFrame({'A': np.arange(0, 25000), 'B': np.arange(0, 25000)})

for i in range(0, 50):
    df.to_csv('{}.csv'.format(i))
Run Code Online (Sandbox Code Playgroud)

方法:

def conc_inside_loop(filenames):
    df = None
    for filename in filenames:

        if df is None:
           df = pd.read_csv(filename)
           continue

        df = pd.concat([df, pd.read_csv(filename)], ignore_index=True)

    return df

def conc_list_comprehension(filenames):
    return pd.concat([pd.read_csv(filename) for filename in filenames], ignore_index=True)
Run Code Online (Sandbox Code Playgroud)

时报:

>> %timeit -n 10 conc_inside_loop(glob.glob('*.csv'))
460 ms ± 15.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


>> %timeit -n 10 conc_list_comprehension(glob.glob('*.csv'))
363 ms ± 32.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

显然列表理解更有效(我已经说过,我明白了).但差异并不大.我不认为你可以调用一个方法穷人被点错了,另一种正确的给我们在这里看到的差异.

正如@piRSquared所述,最后一个问题过于宽泛.但第三种方法是concat在for循环之外使用:

def conc_outside_loop(filenames):

    df_list = []
    for filename in filenames:
        df_list.append(pd.read_csv(filename))

    return pd.concat(df_list, ignore_index=True)

>> %timeit -n 10 conc_outside_loop(glob.glob('*.csv'))
344 ms ± 23.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

piR*_*red 5

  1. 循环和使用concat多个数据源来创建一个或多个DataFrame如此差的实例,这是错误的吗?

是! 熊猫很棒.但是你应该不惜一切代价避免不必要的Pandas对象生成.创建Pandas对象可能很昂贵,DataFrames比Series更多,但这可能True适用于所有python.对于"批评"方法:在循环中创建一个Pandas对象,该对象将在循环的下一次迭代中被覆盖.您应该考虑如何收集数据以便在收集结束时生成Pandas对象.

  1. 在这种情况下我们是否应该始终使用列表理解?

没有!正如我上面所说,将其视为收集数据以准备构建Pandas对象.理解只是收集的一种方式.

  1. 文档似乎不建议既不使用列表推理也不使用循环,那么从多个数据源创建(s)的推荐方法是DataFrame什么?

这太宽泛了.可以针对许多方法进行说明.只是不要使用concatappend循环.我每次都会说错了.

并且"每次"我实际上并不是"每次".我DO的意思是,你不应该一个循环,然后循环之前,并在每次迭代经历的东西附加在现有数据框初始化的麻烦建立在某些时候数据帧.每次迭代都变得非常昂贵.在"已接受"答案的情况下:它将数据帧分配给字典键,然后保持不变.它没有反复搞砸.

  • 你的答案更好,所以我正在删除我的.我会添加OP似乎错过的东西.他描述的循环是在不同的迭代.一个是文件名的完整列表,另一个是带有列表值的分类字典,可迭代的小得多.他们没有可比性. (2认同)
  • 显然这是一个意见问题.如果您这样做,您的结果应该是正确的.但答案通常不仅仅是产生正确的结果.作为回答者,我们应该坚持更高的标准并提出代表良好实践的选择.这需要我们自以为是.我不相信需要一个冗长的答案来解释为什么在循环中这样做是一个坏主意.它本质上是在问"我能创建并覆盖并且构建对象的次数很多,而我可以用很少的时间来构建对象吗?". (2认同)