Skip to content

CSV

CSV 是一种常见的数据交换格式。作为纯文本他具有良好的可读性以及兼容性,并且配合压缩工具能够以很小的存储空间保存。但是他的数据类型表现很弱仅能表示字符串和数字,并且无法区分空值和空字符串。

read_csv()

用于共享数据最佳或者最被广泛支持的就是 csv 文件。pandas 中提供了 pandas.read_csv() 方法来从 csv 文件中获取数据:

Python
pandas.read_csv(
  filepath_or_buffer, # 类文件或类路径对象
  # 解析
  encoding = None,  # 字符编码默认 "utf-8"
  engine = None,    # 解析引擎,"c" | "pyarrow" | "python"
  dialect = None,    # 指定方言
  doublequote = True,  # "" 被作为 " 看待
  quotechar = '"',  # 包围非法字符的符号
  quoting = 0,    #
  compression = 'infer', # 读取的文件是压缩文件,根据扩展名识别,对于打包文件只能包含一个文件

  # 指定分隔符
  sep = _NoDefault.no_default, # 指定分隔符
  delimiter = None,      # 等同于 sep
  delim_whitespace = False,  # 是否使用空白分隔符

  # 设置列和索引
  header:int|int[] = 0,      # 指定第一行作为标题解析默认 0 即第一行
  names = _NoDefault.no_default,  # 如果第一行不存在标题,则指定标题
  index_col = None,        # 指定要作为索引的列
  usecols = None,          # 指定要使用的列,相当于 SELECT * 子句

  # 类型解析
  dtype = None,      # 字典,为列指定数据类型
  dtype_backend = _NoDefault.no_default  # 类型引擎, "numpy_nullable"|"pyarrow"
  converters = None,    # 为每列指定解析函数 {columnname|columnindex: callable}
  true_values = None,    # 指定要视为 True 的值例如 ["true", "yes"]
  false_values = None,  # 指定要视为 False 的值例如 ["false", "no" ]
  decimal = '.',      # 要识别为小数点的字符,在一些国家可能是 ,
  thousands = None,    # 万 分隔符,即 1,2834 需要指定 , 为万分隔符才能够解析数字
  skipinitialspace = False,

  # 跳过行
  skiprows = None,  # 跳过指定行,可以接受一个函数函数接受行号,如果返回结果 True 则跳过
  skipfooter = 0,    # 跳过尾部的指定行数
  nrows = None,    # 读取指定行数
  comment = None,    # 指定不应该解析的行,例如 comment = "#"
  skip_blank_lines = True, # 跳过空行

  # 错误处理
  on_bad_lines = 'error',   # 行错误 "error" | "warn" | "skip"
  encoding_errors = 'strict',  # 编码错误,等同于 open

  # 缺失值处理
  na_values = None,    # 指定要视为 na 的值例如 ["None", "n/a", "null"]
  keep_default_na = True,
  na_filter = True,
  verbose = False,

  # 解析时间类型
  parse_dates = None,    # 指定要解析为时间类型的列
  date_format = None,    # 配合 parse_dates 的解析样式
  infer_datetime_format = _NoDefault.no_default, # 加速选项,现在是默认的
  cache_dates = True,    # 缓存 datetime 解析,这对于大量相同时间的解析非常有用
  date_parser = _NoDefault.no_default,
  dayfirst = False,
  keep_date_col = False,

  #分块读取
  iterator = False,
  chunksize = None,

  # 其他
  lineterminator = None,
  escapechar = None,
  low_memory = True,
  memory_map = False,
  float_precision = None,
  storage_options = None,
  )

看这长长的参数也知道这是一个很重的参数。不过很多时候我们只需要关注基本使用的情形就可以了。通常最基本地使用只需要为 filepath_or_buffer 赋值即可,它可以是 path-like 对象或者 file-like 对象。

指定分隔符

参数 类型 默认值 说明
sep/delimiter string|None ',' 指定分隔符,如果为 None 会自动嗅探
delim_whitespace boolean False 是否使用空白符作为分隔符

Tips

sep 参数如果长度超过一个字符将被解释为正则表达式,并强制使用 python 引擎解析。这意味着 delim_whitespace = True 等价于 sep="\s+"

指定解析引擎

参数 类型 默认值 说明
engine string 'c' 指定引擎

可选的引擎包括三个:

  • "c":速度快,并不支持所有的功能
  • "python":兼容性更好,支持所有的功能
  • "pyarrow":速度同样快,并且支持多线程导入但是很多功能无法正常工作

Tips

默认文件会使用 python 内置的 csv 模块来解析数据,我们也可以通过参数来指定解析引擎。

指定列和索引

参数 类型 默认值 说明
header int|list[int]|None 0 用作列名的行号,0 的含义是将第一行作为列名,也可以指定多行来生成多列名
names list|None None 指定列名,如果设置该选项应当显式指定header=None
index_col int|str|list[int|str]|False|None None 要作为 Index 的列,可以是多列来指定 MultilIndex
usecols list|callable|None None 返回列的子集

其中 index_col 他的默认值即index_col = None会指示 pandas 自动猜测索引的行为,他通常有以下表现:

  1. 如果指定了 names 并且len(names)与实际的列数相匹配,通常就是header = 0的情况。使用range(n)来构造索引
  2. 如果指定了 names 并且len(names)与实际的列数不匹配(少),则使用前面的几列来作为索引(具体取决于少几列)。可以显示指定index_col=False来禁止自动使用前几列作为索引的行为,此时就只能使用range(n)来构造

usercols 定义要构造 DataFrame 的列, 他可以是字符串列表(使用列名来筛选)以整数列表(使用列序列来筛选)。注意顺序是无所谓的,他一定是按照 csv 中哪一个在前使用的那一个

Python
In [1]: import pandas as pd
In [2]: from io import StringIO
In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"
In [4]: pd.read_csv(StringIO(data))
Out[4]:
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3
# 顺序是无所谓的
In [5]: pd.read_csv(StringIO(data), usecols=['col3', 'col1'])
Out[5]:
  col1  col3
0    a     1
1    a     2
2    c     3

Tips

如果是使用的 "c" 或 "pyarrow" 引擎,指定 usercols 能够极大地提高内存使用率和加载效率,因为他会在加载的时候就能跳过他们。而 "python" 引擎则是在加载完成后才进行筛选。

指定数据类型

参数 类型 默认值 说明
dtype dict None 可以是{"column_name_1": np.float64 ...}来为列指定类型
dtype_backend str "numpy" 指定数据类型后端
converters dict None 他可以是{"column_name": callable}这样的形式,可以使用函数来执行一些处理

converters 是一个非常强大的行为,对于一些值可能需要经过一定地转换才能够指定数据类型时非常有用:

Python
df = pd.read_csv(
    "f_liepin_position_0.csv",
    header=0,
    usecols=["flp_fbdate", "flp_id"],
    converters={"flp_fbdate": lambda x: datetime.datetime.strptime(x, "%Y/%m/%d")},
    index_col=False,
)

时间类型解析

convertes 是一个全能的属性,但是比较耗费时间。read_csv 中提供了 parse_dates 参数能够根据传入的参数类型不同表现为多种行为:

  1. boolean:如果为 True 则尝试自动解析,默认为 False
  2. 列名或列索引列表:尝试自动解析对应的列,这也是使用最为广泛的情况,尤其对于 ISO 8601 格式的日期
  3. 二维列表或字典:例如{"foo": [1,3]}将列 1 和列 3 作为一个整体解析然后赋值给foo 列

Tips

如果无法使用 parse_dates 来自动解析,更推荐的方法就是首先导出到 object 类型然后使用 pandas.to_datetime() 来转换。

错误处理

由于 csv 没有一个严格的标准。尤其牵扯到大量字符串时会导致一些行的解释错误,因此如果解析错误可以尝试指定一些参数来跳过错误的内容:

on_bad_lines 参数表示在遇到错误行(包含过多字段的行,通常就是因为字符串中包含了分隔符的情况)时所做的操作:

  • "error": 默认,引发异常
  • "warn": 警告
  • "skip": 自动跳过

encoding_errors 参数则是在遇到编码错误时的操作。他等同于 open 函数的操作方式。

分块处理

对于一个非常大的 csv 文件,由于内存限制通常需要分块处理。这可以使用 iterator 和 chunksize 来实现:

Python
df = pd.read_csv(
    r"f_liepin_position_0.csv",
    header=0,
    usecols=["flp_fbdate", "flp_id"],
    converters={"flp_fbdate": lambda x: datetime.datetime.strptime(x, "%Y/%m/%d")},
    index_col=False,
    iterator=True,
    chunksize=1000,
)
for temp in df:
    print(temp)  # 每个 temp 有 1000 行数据。

Tips

iterator = True 的含义是 read_csv 的结果是一个 TextFileReader 对象,他是一个生成器对象我们可以使用迭代工具来迭代他。而他具体包含多少行数据由 chunksize 参数来指定。

to_csv()

DataFrame 中都包含 to_csv 方法用于将 DataFrame 对象导出到 csv 文件中:

Python
DataFrame.to_csv(
  path_or_buf, # 文件路径,如果是文件对象必须使用 newline='' 打开
  sep=',',   # 输出文件的分隔符
  na_rep='', # 缺失值的字符串表示,默认空字符串
  float_format=None, # 浮点数的格式化字符串
  columns=None,  # 要写入的列,默认全部
  header=True,   # 是否写入列名(即第一行输出列名)
  index=True,    # 是否写入索引
  index_label=None,  # 如果 index=True 指定索引列列名
  mode='w',      # python 的写入模式,对于大文件可以通过 "a" 来添加
  encoding=None, # 指定编码
  compression='infer', # 是否启用压缩,默认会根据文件路径名来自动适配
  quoting=None,  # 设置引号规则
  quotechar='"', # 用于包围具有特殊字符的字段的字符
  lineterminator=None, # 表示行尾的字符,即 "\r" "\n" 还是 "\n\r"
  chunksize=None,   # 一次写入的行数
  date_format=None, # datetime 对象的格式化字符串
  doublequote=True, # 是否处理 quotchar。即 " 会写作 ""
  escapechar=None,  # 用于在适当时转义 sep 和 quotechar
  decimal='.',      # 小数点
  errors='strict',  # 错误处理方式
  storage_options=None
)

大部分时候都不需要修改上面的参数,直接输出是兼容性最好的行为。

追加文件

有时候需要向现有的文件中追加或者需要写入一个非常大的文件时需要分块写入,此时就可以通过 mode='a' 来实现,此时需要注意的就是只需要第一个文件中 header=True 其他的都应该 header=False:

Python
import math

onces = 200000

for i in range(math.ceil(72753081/onces)):
    if i == 0:
        df = datas[:onces].to_pandas()
    # 第一个正常写入
        df.to_csv('招聘数据_前程无忧.csv', index=False)
    else:
        df = datas[i*onces:(i+1)*onces].to_pandas()
    # 之后的 mode='a' header=False
        df.to_csv('招聘数据_前程无忧.csv', index=False, mode='a', header=False)
    print(f"很成功写入{(i+1)*onces}")

参考