将多个CSV文件导入pandas并合并为一个DataFrame

757

我想从一个目录中读取多个CSV文件并将它们连接成一个大的DataFrame。但是我还没有找到解决方法。这是我目前的代码:

import glob
import pandas as pd

# Get data file names
path = r'C:\DRO\DCL_rawdata_files'
filenames = glob.glob(path + "/*.csv")

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

# Concatenate all data into one DataFrame
big_frame = pd.concat(dfs, ignore_index=True)

我想我需要在for循环中寻求一些帮助?

20个回答

868

请查看Pandas: IO工具了解所有可用的.read_方法。

如果所有CSV文件都具有相同的列,请尝试以下代码。

我已经添加了header=0,这样在读取CSV文件的第一行后,它就可以被分配为列名。

import pandas as pd
import glob
import os

path = r'C:\DRO\DCL_rawdata_files' # use your path
all_files = glob.glob(os.path.join(path , "/*.csv"))

li = []

for filename in all_files:
    df = pd.read_csv(filename, index_col=None, header=0)
    li.append(df)

frame = pd.concat(li, axis=0, ignore_index=True)

或者,归功于来自Sid的评论。

all_files = glob.glob(os.path.join(path, "*.csv"))

df = pd.concat((pd.read_csv(f) for f in all_files), ignore_index=True)

  • 通常需要对每个数据样本进行标识,可以通过向数据帧添加新列来实现。
  • 本示例将使用标准库中的pathlib。它将路径视为具有方法的对象,而不是要切片的字符串。

导入和设置

from pathlib import Path
import pandas as pd
import numpy as np

path = r'C:\DRO\DCL_rawdata_files'  # or unix / linux / mac path

# Get the files from the path provided in the OP
files = Path(path).glob('*.csv')  # .rglob to get subdirectories

选项1:

  • 添加一个包含文件名的新列
dfs = list()
for f in files:
    data = pd.read_csv(f)
    # .stem is method for pathlib objects to get the filename w/o the extension
    data['file'] = f.stem
    dfs.append(data)

df = pd.concat(dfs, ignore_index=True)

选项2:

  • 使用enumerate添加一个通用名称的新列
dfs = list()
for i, f in enumerate(files):
    data = pd.read_csv(f)
    data['file'] = f'File {i}'
    dfs.append(data)

df = pd.concat(dfs, ignore_index=True)

选项 3:

  • 使用列表推导式创建数据帧,然后使用np.repeat添加新列。
    • [f'S{i}' for i in range(len(dfs))] 创建一个字符串列表来命名每个数据帧。
    • [len(df) for df in dfs] 创建一个长度列表
  • 此选项的归属权归该绘图答案所有。
# Read the files into dataframes
dfs = [pd.read_csv(f) for f in files]

# Combine the list of dataframes
df = pd.concat(dfs, ignore_index=True)

# Add a new column
df['Source'] = np.repeat([f'S{i}' for i in range(len(dfs))], [len(df) for df in dfs])

选项4:

  • 使用.assign创建新列的一行代码,该方法归功于C8H10N4O2的评论。
df = pd.concat((pd.read_csv(f).assign(filename=f.stem) for f in files), ignore_index=True)
或者
df = pd.concat((pd.read_csv(f).assign(Source=f'S{i}') for i, f in enumerate(files)), ignore_index=True)

447
使用更简洁的方式,可能执行更快,而且不使用列表:df = pd.concat(pd.read_csv(f) for f in all_files)此外,建议使用 os.path.join(path, "*.csv") 而不是 path + "/*.csv",这样可以使其具备操作系统的独立性。 - Sid
这是一个非常好的答案! - Adam Jaamour

367

一个darindaCoder的答案的替代方案:

path = r'C:\DRO\DCL_rawdata_files'                     # use your path
all_files = glob.glob(os.path.join(path, "*.csv"))     # advisable to use os.path.join as this makes concatenation OS independent

df_from_each_file = (pd.read_csv(f) for f in all_files)
concatenated_df   = pd.concat(df_from_each_file, ignore_index=True)
# doesn't create a list, nor does it append to one

5
@Mike @Sid 最后两行可以替换为:pd.concat((pd.read_csv(f) for f in all_files), ignore_index=True)。这里的内部括号是Pandas版本0.18.1所需的。 - Dr Fabio Gori
16
建议使用glob.iglob而不是glob.glob;第一个返回的是迭代器(而不是列表) - toto_tico

120
import glob
import os
import pandas as pd   
df = pd.concat(map(pd.read_csv, glob.glob(os.path.join('', "my_files*.csv"))))

5
优秀的一行代码,特别适用于不需要使用read_csv参数的情况! - rafaelvalle
26
如果需要论据,可以使用lambda函数进行操作:df = pd.concat(map(lambda file: pd.read_csv(file, delim_whitespace=True), data_files))。该语句将读取多个数据文件并将它们在纵向方向上拼接成一个DataFrame对象。其中lambda file: pd.read_csv(file, delim_whitespace=True)是一个匿名函数,将每个文件名作为输入参数,并使用pd.read_csv()来读取以空格为分隔符的csv文件。map()函数则将该lambda函数应用于所有数据文件,返回一个可迭代对象,最后通过pd.concat()函数将其合并为单个DataFrame。 - fiedl
2
使用 functools.partial^ 来避免使用 lambda 函数。 - cs95

92
几乎所有答案都过于复杂(全局模式匹配)或依赖于额外的第三方库。您可以使用Pandas和Python内置的所有功能,在两行代码中完成此操作。(适用于几个文件的情况- 一行代码)
df = pd.concat(map(pd.read_csv, ['d1.csv', 'd2.csv','d3.csv']))

对于许多文件

import os

filepaths = [f for f in os.listdir(".") if f.endswith('.csv')]
df = pd.concat(map(pd.read_csv, filepaths))

不使用标题

如果你想要用pd.read_csv函数进行一些特定的更改(例如,不使用标题),可以编写一个单独的函数,并在调用时使用映射。

def f(i):
    return pd.read_csv(i, header=None)

df = pd.concat(map(f, filepaths))

这个pandas语句设置了df,并利用了三个元素:

  1. Python 的 map (function, iterable) 函数向函数(pd.read_csv())发送可迭代对象(我们的列表,即 filepaths 中的每个 CSV 元素)。
  2. Pandas 的 read_csv() 函数像平常一样读入每个 CSV 文件。
  3. Pandas 的 concat() 将它们全部合并到一个 df 变量下。

5
可以直接使用以下代码将多个csv文件合并成一个DataFrame: df = pd.concat(map(pd.read_csv, glob.glob('data/*.csv'))) 其中,glob.glob('data/*.csv')返回了所有在"data"文件夹下的csv文件路径列表,pd.read_csv用于读取csv文件并返回一个DataFrame对象,map函数将pd.read_csv应用到每个csv文件路径上并返回迭代器。最后,pd.concat将所有DataFrame对象按行合并成一个大的DataFrame对象。 - muon
1
我尝试了@muon提供的方法。但是,我有多个带有标题的文件(标题是共同的)。我不想让它们在数据框中连接起来。你知道我该怎么做吗?我尝试了df = pd.concat(map(pd.read_csv(header=0), glob.glob('data/*.csv)),但是它报错了:“parser_f() missing 1 required positional argument: 'filepath_or_buffer'”。 - cadip92
你提问已经有一段时间了...但我更新了我的答案,包括没有标题的答案(或者如果你想传递任何更改给read_csv)。 - robmsmt

83

易用且快速

导入两个或多个CSV文件,无需制作名称列表。

import glob
import pandas as pd

df = pd.concat(map(pd.read_csv, glob.glob('data/*.csv')))

4
我们如何将参数传递给这个语法? - delimiter
我的回答:https://dev59.com/QmEi5IYBdhLWcg3w2POB#69994928,灵感来自于这个特定的答案! - Milan
@delimiter,要插入文档的文件路径,请将单词“data”替换为您的文件路径,并保留结尾处的斜杠“/”。 - BGG16

63

1
类似于这样,在pandas API中应该有一个函数来读取目录中的多个文件。显然,它现在还没有。 - Shiv Krishna Jaiswal
这个也适用于AWS S3路径吗? - xmar

23

我通过Google找到了Gaurav Singh的答案

然而,最近我发现使用NumPy进行任何操作都比迭代地直接操作数据框更快,并且在这个解决方案中似乎也有效。

我真诚地希望任何访问这个页面的人都考虑这种方法,但我不想将这个巨大的代码片段作为评论附加在其中,从而使其难以阅读。

您可以利用NumPy来加速数据框连接。

import os
import glob
import pandas as pd
import numpy as np

path = "my_dir_full_path"
allFiles = glob.glob(os.path.join(path,"*.csv"))


np_array_list = []
for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    np_array_list.append(df.as_matrix())

comb_np_array = np.vstack(np_array_list)
big_frame = pd.DataFrame(comb_np_array)

big_frame.columns = ["col1", "col2"....]

计时统计:

total files :192
avg lines per file :8492
--approach 1 without NumPy -- 8.248656988143921 seconds ---
total records old :1630571
--approach 2 with NumPy -- 2.289292573928833 seconds ---

有任何数据支持“加速”吗?具体来说,它是否比https://dev59.com/QmEi5IYBdhLWcg3w2POB#Wq6gEYcBWogLw_1b50kJ更快? - ivan_pozdeev
我没有看到楼主询问如何加快他的连接速度,这只是对一个已经被接受的答案的重新制作。 - pydsigner
4
如果数据包含混合列类型,那么这种方法就不可行。 - Pimin Konstantin Kefaloukos
2
@SKG 很完美..这是我唯一可行的解决方案。500个文件,总共400k行,在2秒内完成。感谢您的发布。 - FrankC
2
5秒内处理1500个文件和750k行数据。太棒了@SKG - Javier
显示剩余2条评论

17

使用map的一行代码,但是如果您想指定额外的参数,可以这样做:

import pandas as pd
import glob
import functools

df = pd.concat(map(functools.partial(pd.read_csv, sep='|', compression=None),
                    glob.glob("data/*.csv")))

注意:单独使用map无法提供额外的参数。


14

如果你想在(Python 3.5或更高版本)中进行递归搜索,你可以按照以下步骤操作:

from glob import iglob
import pandas as pd

path = r'C:\user\your\path\**\*.csv'

all_rec = iglob(path, recursive=True)     
dataframes = (pd.read_csv(f) for f in all_rec)
big_dataframe = pd.concat(dataframes, ignore_index=True)

请注意,最后三行可以用一行代码表示:

df = pd.concat((pd.read_csv(f) for f in iglob(path, recursive=True)), ignore_index=True)

您可以在这里找到**的文档。另外,我使用了iglob而不是glob,因为它返回一个迭代器而不是列表。



编辑:多平台递归函数:

您可以将上述内容包裹在一个多平台函数(Linux、Windows、Mac)中,这样您就可以执行以下操作:

df = read_df_rec('C:\user\your\path', *.csv)

这是函数:

from glob import iglob
from os.path import join
import pandas as pd

def read_df_rec(path, fn_regex=r'*.csv'):
    return pd.concat((pd.read_csv(f) for f in iglob(
        join(path, '**', fn_regex), recursive=True)), ignore_index=True)

9

灵感来源于MrFun答案

import glob
import pandas as pd

list_of_csv_files = glob.glob(directory_path + '/*.csv')
list_of_csv_files.sort()

df = pd.concat(map(pd.read_csv, list_of_csv_files), ignore_index=True)

注:

  1. By default, the list of files generated through glob.glob is not sorted. On the other hand, in many scenarios, it's required to be sorted e.g. one may want to analyze number of sensor-frame-drops v/s timestamp.

  2. In pd.concat command, if ignore_index=True is not specified then it reserves the original indices from each dataframes (i.e. each individual CSV file in the list) and the main dataframe looks like

        timestamp    id    valid_frame
    0
    1
    2
    .
    .
    .
    0
    1
    2
    .
    .
    .
    

    With ignore_index=True, it looks like:

        timestamp    id    valid_frame
    0
    1
    2
    .
    .
    .
    108
    109
    .
    .
    .
    

    IMO, this is helpful when one may want to manually create a histogram of number of frame drops v/s one minutes (or any other duration) bins and want to base the calculation on very first timestamp e.g. begin_timestamp = df['timestamp'][0]

    Without, ignore_index=True, df['timestamp'][0] generates the series containing very first timestamp from all the individual dataframes, it does not give just a value.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接