Python 数据处理 —— pandas csv 文件读写

前言

前面我们介绍了 pandas 的基础语法操作,下面我们开始介绍 pandas 的数据读写操作。

pandasIO API 是一组顶层的 reader 函数,比如 pandas.read_csv(),会返回一个 pandas 对象。

而相应的 writer 函数是对象方法,如 DataFrame.to_csv()

下面列出了所有的 readerwriter 函数

image.png

注意:后面会用到 StringIO,请确保导入

# python3
from io import StringIO
# python2
from StringIO import StringIO

CSV 和文本文件

pandas 用于读取文本文件的主要函数是 read_csv(),可以读取固定分隔符的文件,如 csvtxttsv 以及 xls

1 参数解析

read_csv() 接受以下常用参数:

1.1 基础

filepath_or_buffer: 变量

  • 可以是文件路径、文件 URL 或任何带有 read() 函数的对象

sep: str,默认 ,,对于 read_table\t

  • 文件分隔符,如果设置为 None,则 C 引擎无法自动检测分隔符,而 Python 引擎可以通过内置的嗅探器工具自动检测分隔符。
  • 此外,如果设置的字符长度大于 1,且不是 '\s+',那么该字符串会被解析为正则表达式,且强制使用 Python 解析引擎。
  • 例如 '\\r\\t',但是正则表达式容易忽略文本中的引用数据。

delimiter: str, 默认为 None

  • sep 的替代参数,功能一致
1.2 列、索引、名称

header: intlist, 默认为 'infer'

  • 用作列名的行号,默认行为是对列名进行推断:
    • 如果未指定 names 参数其行为类似于 header=0,即从读取的第一行开始推断。
    • 如果设置了 names,则行为与 header=None 相同。
  • 也可以为 header 设置列表,表示多级列名。如 [0,1,3],未指定的行(这里是 2)将会被跳过,如果 skip_blank_lines=True,则会跳过空行和注释的行。因此 header=0 并不是代表文件的第一行

names: array-like, 默认为 None

  • 需要设置的列名列表,如果文件中不包含标题行,则应显式传递 header=None,且此列表中不允许有重复值。

index_col: int, str, sequence of int/str, False, 默认为 None

  • 用作 DataFrame 的索引的列,可以字符串名称或列索引的形式给出。如果指定了列表,则使用 MultiIndex

  • 注意index_col=False 可用于强制 pandas 不要将第一列用作索引。例如,当您的文件是每行末尾都带有一个分隔符的错误文件时。

usecols: 列表或函数, 默认为 None

  • 只读取指定的列。如果是列表,则所有元素都必须是位置(即文件列中的整数索引)或字符串,这些字符串必须与 names 参数提供的或从文档标题行推断出的列名相对应。

  • 列表中的顺序会被忽略,即 usecols=[0, 1] 等价于 [1, 0]

  • 如果是可调用函数,将会根据列名计算,返回可调用函数计算为 True 的名称

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=lambda x: x.upper() in ["COL1", "COL3"])
Out[5]: 
  col1  col3
0    a     1
1    a     2
2    c     3

使用此参数可以大大加快解析时间并降低内存使用

squeeze: boolean, 默认为 False

  • 如果解析的数据只包含一列,那么返回一个 Series

prefix: str, 默认为 None

  • 当没有标题时,添加到自动生成的列号的前缀,例如 'X' 表示 X0, X1

mangle_dupe_cols: boolean, 默认为 True

  • 重复的列将被指定为 'X','X.1''X.N',而不是 'X'… 。如果在列中有重复的名称,传递 False 将导致数据被覆盖
1.3 常规解析配置

dtype: 类型名或类型字典(column -> type), 默认为 None

  • 数据或列的数据类型。例如。 {'a':np.float64,'b':np.int32}

engine: {'c', 'python'}

  • 要使用的解析器引擎。C 引擎更快,而 Python 引擎目前功能更完整

converters: dict, 默认为 None

  • 用于在某些列中对值进行转换的函数字典。键可以是整数,也可以是列名

true_values: list, 默认为 None

  • 数据值解析为 True

false_values: list, 默认为 None

  • 数据值解析为 False

skipinitialspace: boolean, 默认为 False

  • 跳过分隔符之后的空格

skiprows: 整数或整数列表, 默认为 None

  • 在文件开头要跳过的行号(索引为 0)或要跳过的行数

  • 如果可调用函数,则对索引应用函数,如果返回 True,则应跳过该行,否则返回 False

In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [7]: pd.read_csv(StringIO(data))
Out[7]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
  col1 col2  col3
0    a    b     2

skipfooter: int, 默认为 0

  • 需要跳过文件末尾的行数(不支持 C 引擎)

nrows: int, 默认为 None

  • 要读取的文件行数,对于读取大文件很有用

memory_map: boolean, 默认为 False

  • 如果为 filepath_or_buffer 参数指定了文件路径,则将文件对象直接映射到内存中,然后直接从那里访问数据。使用此选项可以提高性能,因为不再有任何 I/O 开销
1.4 NA 和缺失数据处理

na_values: scalar, str, list-like, dict, 默认为 None

  • 需要转换为 NA 值的字符串

keep_default_na: boolean, 默认为 True

  • 解析数据时是否包含默认的 NaN 值。根据是否传入 na_values,其行为如下

    • keep_default_na=True, 且指定了 na_values, na_values 将会与默认的 NaN 一起被解析

    • keep_default_na=True, 且未指定 na_values, 只解析默认的 NaN

    • keep_default_na=False, 且指定了 na_values, 只解析 na_values 指定的 NaN

    • keep_default_na=False, 且未指定 na_values, 字符串不会被解析为 NaN

  • 注意:如果 na_filter=False,那么 keep_default_nana_values 参数将被忽略

na_filter: boolean, 默认为 True

  • 检测缺失值标记(空字符串和 na_values 的值)。在没有任何 NA 的数据中,设置 na_filter=False 可以提高读取大文件的性能

skip_blank_lines: boolean, 默认为 True

  • 如果为 True,则跳过空行,而不是解释为 NaN
1.5 日期时间处理

parse_dates: 布尔值、列表或嵌套列表、字典, 默认为 False.

  • 如果为 True -> 尝试解析索引

  • 如果为 [1, 2, 3] -> 尝试将 1, 2, 3 列解析为分隔的日期

  • 如果为 [[1, 3]] -> 将 1, 3 列解析为单个日期列

  • 如果为 {'foo': [1, 3]} -> 将 1, 3 列作为日期并设置列名为 foo

infer_datetime_format: 布尔值, 默认为 False

  • 如果设置为 True 且设置了 parse_dates,则尝试推断 datetime 格式以加快处理速度

date_parser: 函数, 默认为 None

  • 用于将字符串序列转换为日期时间实例数组的函数。默认使用 dateutil.parser.parser 进行转换,pandas 将尝试以三种不同的方式调用 date_parser
    • 传递一个或多个数组(parse_dates 定义的列)作为参数;
    • parse_dates 定义的列中的字符串值连接到单个数组中,并将其传递;
    • 使用一个或多个字符串(对应于 parse_dates 定义的列)作为参数,对每一行调用 date_parser 一次。

dayfirst: 布尔值, 默认为 False

  • DD/MM 格式的日期

cache_dates: 布尔值, 默认为 True

  • 如果为 True,则使用唯一的、经过转换的日期缓存来应用 datetime 转换。
  • 在解析重复的日期字符串,特别是带有时区偏移量的日期字符串时,可能会显著提高速度。
1.6 迭代

iterator: boolean, 默认为 False

  • 返回 TextFileReader 对象以进行迭代或使用 get_chunk() 来获取块
1.7 引用、压缩和文件格式

compression: {'infer', 'gzip', 'bz2', 'zip', 'xz', None, dict}, 默认为 'infer'

  • 用于对磁盘数据进行即时解压缩。如果为 "infer",则如果 filepath_or_buffer 是文件路径且以 ".gz"".bz2"".zip"".xz" 结尾,则分别使用 gzipbz2zipxz 解压,否则不进行解压缩。

  • 如果使用 "zip",则 ZIP 文件必须仅包含一个要读取的数据文件。设置为 None 表示不解压

  • 也可以使用字典的方式,键为 method 的值从 {'zip', 'gzip', 'bz2'} 中选择。例如

compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

thousandsstr, 默认为 None

  • 数值在千位的分隔符

decimal: str, 默认为 '.'

  • 小数点

float_precision: string, 默认为 None

  • 指定 C 引擎应该使用哪个转换器来处理浮点值。普通转换器的选项为 None,高精度转换器的选项为 high,双向转换器的选项为 round_trip

quotechar: str (长度为 1)

  • 用于表示被引用数据的开始和结束的字符。带引号的数据里的分隔符将被忽略

comment: str, 默认为 None

  • 用于跳过该字符开头的行,例如,如果 comment='#',将会跳过 # 开头的行

encoding: str, 默认为 None

  • 设置编码格式
1.8 错误处理

error_bad_linesboolean, 默认为 True

  • 默认情况下,字段太多的行(例如,带有太多逗号的 csv 文件)会引发异常,并且不会返回任何 DataFrame
  • 如果设置为 False,则这些坏行将会被删除

warn_bad_linesboolean, 默认为 True

  • 如果 error_bad_lines=Falsewarn_bad_lines=True,每个坏行都会输出一个警告

2. 指定数据列的类型

您可以指示整个 DataFrame 或各列的数据类型

In [9]: import numpy as np

In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"

In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11

In [12]: df = pd.read_csv(StringIO(data), dtype=object)

In [13]: df
Out[13]: 
   a   b   c    d
0  1   2   3    4
1  5   6   7    8
2  9  10  11  NaN

In [14]: df["a"][0]
Out[14]: '1'

In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})

In [16]: df.dtypes
Out[16]: 
a      int64
b     object
c    float64
d      Int64
dtype: object

你可以使用 read_csv()converters 参数,统一某列的数据类型

In [17]: data = "col_1\n1\n2\n'A'\n4.22"

In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})

In [19]: df
Out[19]: 
  col_1
0     1
1     2
2   'A'
3  4.22

In [20]: df["col_1"].apply(type).value_counts()
Out[20]: 
<class 'str'>    4
Name: col_1, dtype: int64

或者,您可以在读取数据后使用 to_numeric() 函数强制转换类型

In [21]: df2 = pd.read_csv(StringIO(data))

In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")

In [23]: df2
Out[23]: 
   col_1
0   1.00
1   2.00
2    NaN
3   4.22

In [24]: df2["col_1"].apply(type).value_counts()
Out[24]: 
<class 'float'>    4
Name: col_1, dtype: int64

它将所有有效的数值转换为浮点数,而将无效的解析为 NaN

最后,如何处理包含混合类型的列取决于你的具体需要。在上面的例子中,如果您只想要将异常的数据转换为 NaN,那么 to_numeric() 可能是您的最佳选择。

然而,如果您想要强制转换所有数据,而无论类型如何,那么使用 read_csv()converters 参数会更好

注意

在某些情况下,读取包含混合类型列的异常数据将导致数据集不一致。

如果您依赖 pandas 来推断列的类型,解析引擎将继续推断数据块的类型,而不是一次推断整个数据集。

In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))

In [26]: df = pd.DataFrame({"col_1": col_1})

In [27]: df.to_csv("foo.csv")

In [28]: mixed_df = pd.read_csv("foo.csv")

In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]: 
<class 'int'>    737858
<class 'str'>    262144
Name: col_1, dtype: int64

In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')

这就导致 mixed_df 对于列的某些块包含 int 类型,而对于其他块则包含 str,这是由于读取的数据是混合类型。

3 分类类型

分类类型可以通过指定 dtype='category'dtype=CategoricalDtype(categories, ordered) 直接解析

In [31]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [32]: pd.read_csv(StringIO(data))
Out[32]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [33]: pd.read_csv(StringIO(data)).dtypes
Out[33]: 
col1    object
col2    object
col3     int64
dtype: object

In [34]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[34]: 
col1    category
col2    category
col3    category
dtype: object

也可以使用字典对指定列设置类型

In [35]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[35]: 
col1    category
col2      object
col3       int64
dtype: object

指定 dtype ='category' 将导致无序分类,其类别是数据中所有观察值的集合。

如果要更好地控制类别和顺序,请提前创建 CategoricalDtype,然后将其传递给该列的 dtype

In [36]: from pandas.api.types import CategoricalDtype

In [37]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)

In [38]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[38]: 
col1    category
col2      object
col3       int64
dtype: object

使用 dtype=CategoricalDtype 时,超出的数据类型将被视为缺失值

In [39]: dtype = CategoricalDtype(["a", "b", "d"])  # No 'c'

In [40]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[40]: 
0      a
1      a
2    NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']

这与 Categorical.set_categories() 的行为相匹配

注意

dtype='category' 时,生成的类别将始终解析为字符串(object 类型)。

如果类别是数字型,则可以使用 to_numeric() 函数或其他转换器进行转换,如 to_datetime()
dtype 是一个同构(所有数字、所有日期时间等)的 CategoricalDtype 时,转换将自动完成

In [41]: df = pd.read_csv(StringIO(data), dtype="category")

In [42]: df.dtypes
Out[42]: 
col1    category
col2    category
col3    category
dtype: object

In [43]: df["col3"]
Out[43]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']

In [44]: df["col3"].cat.categories = pd.to_numeric(df["col3"].cat.categories)

In [45]: df["col3"]
Out[45]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]

4 列的命名和使用

4.1 处理列名

一个文件可能有也可能没有标题行,pandas 默认将第一行用作列名

In [46]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"

In [47]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [48]: pd.read_csv(StringIO(data))
Out[48]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

通过将 names 参数与 header 一起使用

In [49]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [50]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[50]: 
   foo  bar  baz
0    1    2    3
1    4    5    6
2    7    8    9

In [51]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[51]: 
  foo bar baz
0   a   b   c
1   1   2   3
2   4   5   6
3   7   8   9

如果标题不在第一行中,可以将行号传递给 header,将会跳过前面的行

In [52]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"

In [53]: pd.read_csv(StringIO(data), header=1)
Out[53]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

5. 重复列名处理

如果文件或表头包含重复的名称,默认情况下 pandas 会将它们区分开,以防止数据覆盖

In [54]: data = "a,b,a\n0,1,2\n3,4,5"

In [55]: pd.read_csv(StringIO(data))
Out[55]: 
   a  b  a.1
0  0  1    2
1  3  4    5

默认情况下,mangle_dupe_cols=True 会使用 .N 的方式标记重名的列,如果设置 mangle_dupe_cols=False 将会出现重复的列

In [2]: data = 'a,b,a\n0,1,2\n3,4,5'
In [3]: pd.read_csv(StringIO(data), mangle_dupe_cols=False)
Out[3]:
   a  b  a
0  2  1  2
1  5  4  5

为了防止用户遇到重复数据的问题,现在,如果 mangle_dupe_cols != True,则会引发 ValueError 异常:

In [2]: data = 'a,b,a\n0,1,2\n3,4,5'
In [3]: pd.read_csv(StringIO(data), mangle_dupe_cols=False)
...
ValueError: Setting mangle_dupe_cols=False is not supported yet
5.1 筛选列

usecols 参数允许你选择文件中指定的列,可以使用列名、位置或一个可调用的函数

In [56]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"

In [57]: pd.read_csv(StringIO(data))
Out[57]: 
   a  b  c    d
0  1  2  3  foo
1  4  5  6  bar
2  7  8  9  baz

In [58]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[58]: 
   b    d
0  2  foo
1  5  bar
2  8  baz

In [59]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[59]: 
   a  c    d
0  1  3  foo
1  4  6  bar
2  7  9  baz

In [60]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[60]: 
   a  c
0  1  3
1  4  6
2  7  9

usecols 参数还可以用于指定最终结果中不使用哪些列

In [61]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[61]: 
   b    d
0  2  foo
1  5  bar
2  8  baz

在这个例子中,我们排除了 ac

6 注释和空行

6.1 忽略注释行和空行

如果指定了 comment 参数,则注释的行将被忽略。默认情况下,空白的行也会被忽略

In [62]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [63]: print(data)

a,b,c
  
# commented line
1,2,3

4,5,6

In [64]: pd.read_csv(StringIO(data), comment="#")
Out[64]: 
   a  b  c
0  1  2  3
1  4  5  6

如果设置 skip_blank_lines=False,则不会忽略空行和注释行

In [65]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [66]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[66]: 
     a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0

注意:被忽略的行可能会造成涉及行号的歧义,header 参数使用行号(忽略注释/空行),而 skiprows 使用行号(包括注释/空行)

In [67]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [68]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[68]: 
   A  B  C
0  1  2  3

In [69]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [70]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[70]: 
   a  b  c
0  1  2  3

如果 headerskiprows 都指定了,则 header 将相对于 skiprows 的末尾。例如

In [71]: data = (
   ....:     "# empty\n"
   ....:     "# second empty line\n"
   ....:     "# third emptyline\n"
   ....:     "X,Y,Z\n"
   ....:     "1,2,3\n"
   ....:     "A,B,C\n"
   ....:     "1,2.,4.\n"
   ....:     "5.,NaN,10.0\n"
   ....: )
   ....: 

In [72]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0


In [73]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[73]: 
     A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0
6.2 注释

有时文件中可能包含注释或元数据:

In [74]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome

默认情况下,解析器在输出中包括注释

In [75]: df = pd.read_csv("tmp.csv")

In [76]: df
Out[76]: 
         ID    level                        category
0  Patient1   123000           x # really unpleasant
1  Patient2    23000  y # wouldn't take his medicine
2  Patient3  1234018                     z # awesome

我们可以使用 comment 关键字

In [77]: df = pd.read_csv("tmp.csv", comment="#")

In [78]: df
Out[78]: 
         ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 

7 处理 Unicode 数据

encoding 参数应用于编码 unicode 数据,它将导致字节字符串在结果中需要 unicode 解码

In [79]: from io import BytesIO

In [80]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"

In [81]: data = data.decode("utf8").encode("latin-1")

In [82]: df = pd.read_csv(BytesIO(data), encoding="latin-1")

In [83]: df
Out[83]: 
      word  length
0  Träumen       7
1    Grüße       5

In [84]: df["word"][1]
Out[84]: 'Grüße'

一些情况下必须指定正确的解码格式才能正确解析数据

8 索引列和末尾分隔符

如果一个文件的数据列比列名多一列,第一列将被用作 DataFrame 的行名

In [85]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [86]: pd.read_csv(StringIO(data))
Out[86]: 
        a    b     c
4   apple  bat   5.7
8  orange  cow  10.0
In [87]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [88]: pd.read_csv(StringIO(data), index_col=0)
Out[88]: 
            a    b     c
index                   
4       apple  bat   5.7
8      orange  cow  10.0

通常,您可以使用 index_col 参数来实现此行为

当在每个数据行的末尾带有一个分隔符的文件时,会出现一些异常情况,让解析器感到头大。要显式禁用索引列推断并放弃最后一列,可以设置 index_col=False

In [89]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [90]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [91]: pd.read_csv(StringIO(data))
Out[91]: 
        a    b   c
4   apple  bat NaN
8  orange  cow NaN

In [92]: pd.read_csv(StringIO(data), index_col=False)
Out[92]: 
   a       b    c
0  4   apple  bat
1  8  orange  cow

如果使用 usecols 参数提取数据的子集,index_col 的作用将基于该子集,而不是原始数据

In [93]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [94]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [95]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[95]: 
     b   c
4  bat NaN
8  cow NaN

In [96]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[96]: 
     b   c
4  bat NaN
8  cow NaN

注意:虽然使用了 usecols 参数,但是由于末尾的分隔符,导致数据的第一列作为索引而无法使数据正确对齐

9 日期处理

9.1 指定日期列

为了更好地处理 datetime 数据,read_csv() 提供了 parse_datesdate_parser 关键字参数来指定日期/时间格式,以便将输入文本数据转换为 datetime 对象

最简单的一种情况是只传递 parse_dates=True

# Use a column as an index, and parse it as dates.
In [97]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)

In [98]: df
Out[98]: 
            A  B  C
date               
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

# These are Python datetime objects
In [99]: df.index
Out[99]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)

通常情况下,我们可能希望单独存储日期和时间数据,或者单独存储各种日期字段。

parse_dates 参数可用于指定要从中解析日期和/或时间的列组合

您可以为 parse_dates 指定一组列,组合后的日期列将被添加到输出结果的前面(为了不影响现有的列顺序),新的列名将是以 _ 连接的列名

In [100]: print(open("tmp.csv").read())
KORD,19990127, 19:00:00, 18:56:00, 0.8100
KORD,19990127, 20:00:00, 19:56:00, 0.0100
KORD,19990127, 21:00:00, 20:56:00, -0.5900
KORD,19990127, 21:00:00, 21:18:00, -0.9900
KORD,19990127, 22:00:00, 21:56:00, -0.5900
KORD,19990127, 23:00:00, 22:56:00, -0.5900

In [101]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])

In [102]: df
Out[102]: 
                  1_2                 1_3     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

默认情况下,解析器会删除组合前的日期对应的列,但是您可以选择通过 keep_date_col 关键字保留它们

In [103]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
   .....: )
   .....: 

In [104]: df
Out[104]: 
                  1_2                 1_3     0         1          2          3     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  19990127   19:00:00   18:56:00  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  19990127   20:00:00   19:56:00  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  19990127   21:00:00   20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  19990127   21:00:00   21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  19990127   22:00:00   21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  19990127   23:00:00   22:56:00 -0.59

注意,如果您希望将多个列合并为一个日期列,则必须使用嵌套列表。

也就是说,parse_dates=[1,2] 表示第二和第三列应该分别解析为单独的日期列,而 parse_dates=[[1,2]] 表示这两列应该解析为一个单独的列

你也可以使用字典来自定义组合的名称列

In [105]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [106]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)

In [107]: df
Out[107]: 
              nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

如果要将多个文本列解析为单个日期列,那么数据前面会有一个新列。

index_col 是基于这组新的列而不是原始的数据列

In [108]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [109]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=date_spec, index_col=0
   .....: )  # index is the nominal column
   .....: 

In [110]: df
Out[110]: 
                                 actual     0     4
nominal                                            
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

注意
如果列或索引包含无法解析的日期,则整个列或索引将不变地作为 object 数据类型返回。

对于非标准日期时间解析,请在 pd.read_csv 之后使用 to_datetime() 解析

9.2 日期解析函数

解析器允许您指定一个自定义的 date_parser 函数,以充分利用日期解析 API 的灵活性

In [111]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=date_spec, date_parser=pd.to_datetime
   .....: )
   .....: 

In [112]: df
Out[112]: 
              nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

pandas 将尝试以三种不同的方式调用 date_parser 函数。如果抛出异常,则尝试下一种方式

  1. date_parser 首先使用一个或多个数组作为参数被调用,这些数组是使用 parse_dates 定义的(例如,date_parser(['2013', '2013'], ['1', '2'])

  2. 如果 1 失败了,则会调用 date_parser ,并将所有列按行连接到单个数组中(例如,date_parser(['2013 1','2013 2'])

注意,在性能方面,你应该尝试按以下顺序来解析日期

  1. 尝试使用 infer_datetime_format=True 来推断格式
  2. 如果你知道格式,可以使用 pd.to_datetime(): date_parser=lambda x: pd.to_datetime(x, format=...)
  3. 如果是非标准时间日期格式,请使用自定义 date_parser 函数
9.3 解析带有混合时区的 CSV

pandas 本身不能表示带有混合时区的列或索引。如果 CSV 文件包含混合时区的列,则默认结果将是一个字符串类型的列,即使使用了 parse_dates 也一样

In [113]: content = """\
   .....: a
   .....: 2000-01-01T00:00:00+05:00
   .....: 2000-01-01T00:00:00+06:00"""
   .....: 

In [114]: df = pd.read_csv(StringIO(content), parse_dates=["a"])

In [115]: df["a"]
Out[115]: 
0    2000-01-01 00:00:00+05:00
1    2000-01-01 00:00:00+06:00
Name: a, dtype: object

要将混合时区值解析为 datetime 类型,需要传递一个部分应用的 to_datetime(),且设置参数 utc=True 作为 date_parser

In [116]: df = pd.read_csv(
   .....:     StringIO(content),
   .....:     parse_dates=["a"],
   .....:     date_parser=lambda col: pd.to_datetime(col, utc=True),
   .....: )
   .....: 

In [117]: df["a"]
Out[117]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]
9.4 推断 datetime 格式

如果您为部分或所有列启用了 parse_dates,并且 datetime 字符串都是相同的格式,那么通过设置 infer_datetime_format=True,可以大大提升解析速度。

如果无法猜测格式,或者猜测的格式不能正确解析整个字符串列,将会使用通用的解析方式

下面是一些可以猜测的 datetime 字符串的例子(都表示 2011123000:00:00)

  • 20111230
  • 2011/12/30
  • 20111230 00:00:00
  • 12/30/2011 00:00:00
  • 30/Dec/2011 00:00:00
  • 30/December/2011 00:00:00

注意infer_datetime_formatdayfirst 敏感。如果 dayfirst=True,它就会把 01/12/2011 认为是 121 日。dayfirst=False(默认值)将把 01/12/2011 猜测为 112

# Try to infer the format for the index column
In [118]: df = pd.read_csv(
   .....:     "foo.csv",
   .....:     index_col=0,
   .....:     parse_dates=True,
   .....:     infer_datetime_format=True,
   .....: )
   .....: 

In [119]: df
Out[119]: 
            A  B  C
date               
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5
9.5 国际日期格式

虽然美国的日期格式往往是 MM/DD/YYYY,但许多国际格式使用 DD/MM/YYYY 代替。为方便起见,提供了 dayfirst 关键字

In [120]: print(open("tmp.csv").read())
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c

In [121]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[121]: 
        date  value cat
0 2000-01-06      5   a
1 2000-02-06     10   b
2 2000-03-06     15   c

In [122]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[122]: 
        date  value cat
0 2000-06-01      5   a
1 2000-06-02     10   b
2 2000-06-03     15   c
9.6 将 CSV 写入二进制文件对象

df.to_csv(..., mode="wb") 允许将 CSV 写入打开的二进制模式的文件对象。

在大多数情况下,无需指定模式,因为 Pandas 会自动检测文件对象是以文本模式还是二进制模式打开的

In [123]: import io

In [124]: data = pd.DataFrame([0, 1, 2])

In [125]: buffer = io.BytesIO()

In [126]: data.to_csv(buffer, encoding="utf-8", compression="gzip")

10 指定浮点数转换方法

可以在 C 引擎解析期间使用 float_precision 参数来指定浮点数转换器

该参数有三个可选的值:

  • None: 普通转换器
  • high: 高精度转换器
  • round_trip: 保证文件读写之后小数点精度不变
In [127]: val = "0.3066101993807095471566981359501369297504425048828125"

In [128]: data = "a,b,c\n1,2,{0}".format(val)

In [129]: abs(
   .....:     pd.read_csv(
   .....:         StringIO(data),
   .....:         engine="c",
   .....:         float_precision=None,
   .....:     )["c"][0] - float(val)
   .....: )
   .....: 
Out[129]: 5.551115123125783e-17

In [130]: abs(
   .....:     pd.read_csv(
   .....:         StringIO(data),
   .....:         engine="c",
   .....:         float_precision="high",
   .....:     )["c"][0] - float(val)
   .....: )
   .....: 
Out[130]: 5.551115123125783e-17

In [131]: abs(
   .....:     pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
   .....:     - float(val)
   .....: )
   .....: 
Out[131]: 0.0

11 千位分隔符

对于使用千位分隔符编写的大数,可以将千位关键字设置为长度为 1 的字符串,以便正确解析整数

而在默认情况下,带有千位分隔符的数字将被解析为字符串

In [132]: print(open("tmp.csv").read())
ID|level|category
Patient1|123,000|x
Patient2|23,000|y
Patient3|1,234,018|z

In [133]: df = pd.read_csv("tmp.csv", sep="|")

In [134]: df
Out[134]: 
         ID      level category
0  Patient1    123,000        x
1  Patient2     23,000        y
2  Patient3  1,234,018        z

In [135]: df.level.dtype
Out[135]: dtype('O')

可以设置 thousands 参数来解析

In [136]: print(open("tmp.csv").read())
ID|level|category
Patient1|123,000|x
Patient2|23,000|y
Patient3|1,234,018|z

In [137]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")

In [138]: df
Out[138]: 
         ID    level category
0  Patient1   123000        x
1  Patient2    23000        y
2  Patient3  1234018        z

In [139]: df.level.dtype
Out[139]: dtype('int64')

12 NA 值

要控制哪些值被解析为缺失值(用 NaN 表示),请在 na_values 中指定一个字符串。

如果指定了一个字符串列表,那么其中的所有值都被认为是缺失值

如果您指定一个数字(如浮点数 5.0 或整数 5),相应的等效值也将被认为是一个缺失的值(在这种情况下 [5.0,5] 被有效地识别为 NaN)。

要完全覆盖会被识别为缺失的默认值,请指定 keep_default_na=False

默认被识别为 NaN 的值是

['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', '']

考虑下面额例子

pd.read_csv("path_to_file.csv", na_values=[5])

在上述示例中,除了默认值之外,55.0 将被识别为 NaN。字符串将首先被解释为数字 5,然后被解释为 NaN

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])

在上面的例子中,只有空白字段会被识别为 NaN

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])

上例中,字符串 NA0 都会被识别为 NaN

pd.read_csv("path_to_file.csv", na_values=["Nope"])

除了默认值外,字符串 Nope 也会被识别为 NaN

13 无限值

inf 类似的值将被解析为 np.inf (正无穷)和 -inf-np.inf(负无穷)。

解析会忽略值的大小写,即 Inf,也将被解析为 np.inf

14 返回 Series

使用 squeeze 关键字参数,将返回单个列的 Series 形式输出:

In [140]: print(open("tmp.csv").read())
level
Patient1,123000
Patient2,23000
Patient3,1234018

In [141]: output = pd.read_csv("tmp.csv", squeeze=True)

In [142]: output
Out[142]: 
Patient1     123000
Patient2      23000
Patient3    1234018
Name: level, dtype: int64

In [143]: type(output)
Out[143]: pandas.core.series.Series

15 布尔值

常见的值 TrueFalse 都被认为是布尔值。有时您可能想要识别其他值为布尔值。

为此,可以使用 true_valuesfalse_values 参数,如下所示:

In [144]: data = "a,b,c\n1,Yes,2\n3,No,4"

In [145]: print(data)
a,b,c
1,Yes,2
3,No,4

In [146]: pd.read_csv(StringIO(data))
Out[146]: 
   a    b  c
0  1  Yes  2
1  3   No  4

In [147]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[147]: 
   a      b  c
0  1   True  2
1  3  False  4

16 处理错误的行

某些文件的行格式存在错误,字段太少或太多。字段太少的行将在尾随字段中填充 NA 值。默认情况下,包含太多字段的行将引发错误

In [148]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [149]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError                               Traceback (most recent call last)
<ipython-input-5-6388c394e6b8> in <module>
----> 1 pd.read_csv(StringIO(data))

~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, dialect, error_bad_lines, warn_bad_lines, delim_whitespace, low_memory, memory_map, float_precision)
    684     )
    685
--> 686     return _read(filepath_or_buffer, kwds)
    687
    688

~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py in _read(filepath_or_buffer, kwds)
    456
    457     try:
--> 458         data = parser.read(nrows)
    459     finally:
    460         parser.close()

~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py in read(self, nrows)
   1194     def read(self, nrows=None):
   1195         nrows = _validate_integer("nrows", nrows)
-> 1196         ret = self._engine.read(nrows)
   1197
   1198         # May alter columns / col_dict

~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py in read(self, nrows)
   2153     def read(self, nrows=None):
   2154         try:
-> 2155             data = self._reader.read(nrows)
   2156         except StopIteration:
   2157             if self._first_chunk:

pandas/_libs/parsers.pyx in pandas._libs.parsers.TextReader.read()

pandas/_libs/parsers.pyx in pandas._libs.parsers.TextReader._read_low_memory()

pandas/_libs/parsers.pyx in pandas._libs.parsers.TextReader._read_rows()

pandas/_libs/parsers.pyx in pandas._libs.parsers.TextReader._tokenize_rows()

pandas/_libs/parsers.pyx in pandas._libs.parsers.raise_parser_error()

ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4

你可以选择跳过错误的行

In [29]: pd.read_csv(StringIO(data), error_bad_lines=False)
b'Skipping line 3: expected 3 fields, saw 4\n'
Out[29]:
   a  b   c
0  1  2   3
1  8  9  10

你也可以使用 usecols 参数来消除某些行中出现的多余的列数据

In [30]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])

 Out[30]:
    a  b   c
 0  1  2   3
 1  4  5   6
 2  8  9  10

17 dialect

dialect 参数在读取特定格式的文件时提供了更大的灵活性,默认情况下,它使用 Excel 方言,但您可以指定方言名称或 csv.Dialect 实例

假设您的数据带有未封闭的引号

In [150]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f

默认情况下,read_csv 使用 Excel 方言,并将双引号视为引号字符,这会导致在换行符之前无法找到配对的双引号。

我们可以用 dialect 来解决这个问题:

In [151]: import csv

In [152]: dia = csv.excel()

In [153]: dia.quoting = csv.QUOTE_NONE

In [154]: pd.read_csv(StringIO(data), dialect=dia)
Out[154]: 
       label1 label2 label3
index1     "a      c      e
index2      b      d      f

可以通过关键字参数分别指定所有方言选项

In [155]: data = "a,b,c~1,2,3~4,5,6"

In [156]: pd.read_csv(StringIO(data), lineterminator="~")
Out[156]: 
   a  b  c
0  1  2  3
1  4  5  6

另一个常见的方言选项是 skipinitialspace,跳过分隔符后的任何空白字符

In [157]: data = "a, b, c\n1, 2, 3\n4, 5, 6"

In [158]: print(data)
a, b, c
1, 2, 3
4, 5, 6

In [159]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[159]: 
   a  b  c
0  1  2  3
1  4  5  6

解析器会尽一切努力做正确的事情,类型推断非常重要

18 引号和转义符

嵌入字段中的引号(和其他转义字符)可以通过多种方式处理。

一种方法是使用反斜杠。要正确解析此数据,您应该传递 escapechar 选项:

In [160]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'

In [161]: print(data)
a,b
"hello, \"Bob\", nice to see you",5

In [162]: pd.read_csv(StringIO(data), escapechar="\\")
Out[162]: 
                               a  b
0  hello, "Bob", nice to see you  5

19 固定列宽文件

read_fwf() 函数可用于读取具有固定列宽的数据文件

read_fwf 的函数参数与 read_csv 的函数参数基本相同,只是有两个额外的参数,而且 delimiter 参数的用法也不同:

  • colspecs:一个元组列表,给出每行固定宽度字段的范围,半开区间。默认设置为 infer 可以让解析器自动从数据的前 100 行中推断出格式。
  • widths:一个字段宽度列表,如果间隔是连续的,可以用它来代替 colspecs
  • delimiter:在固定宽度的文件中作为填充字符。如 ~

考虑如下固定宽度文件

In [163]: print(open("bar.csv").read())
id8141    360.242940   149.910199   11950.7
id1594    444.953632   166.985655   11788.4
id1849    364.136849   183.628767   11806.2
id1230    413.836124   184.375703   11916.8
id1948    502.953953   173.237159   12468.3

为了将此文件解析为 DataFrame,我们只需要将列与文件名一起提供给 read_fwf 函数

# Column specifications are a list of half-intervals
In [164]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]

In [165]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)

In [166]: df
Out[166]: 
                 1           2        3
0                                      
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3

另外,您可以仅提供连续列的列宽:

# Widths are a list of integers
In [167]: widths = [6, 14, 13, 10]

In [168]: df = pd.read_fwf("bar.csv", widths=widths, header=None)

In [169]: df
Out[169]: 
        0           1           2        3
0  id8141  360.242940  149.910199  11950.7
1  id1594  444.953632  166.985655  11788.4
2  id1849  364.136849  183.628767  11806.2
3  id1230  413.836124  184.375703  11916.8
4  id1948  502.953953  173.237159  12468.3

解析器会忽略列周围的多余空白,因此可以在文件中的列之间留出额外的分隔。

默认情况下,read_fwf 会尝试使用文件的前 100 行来推断文件的 colspec

仅当文件的列能够通过提供的定界符对齐并正确分隔时,才可以这样做

In [170]: df = pd.read_fwf("bar.csv", header=None, index_col=0)

In [171]: df
Out[171]: 
                 1           2        3
0                                      
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3

read_fwf 支持 dtype 参数,用于指定与推断类型不同的列类型

In [172]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[172]: 
1    float64
2    float64
3    float64
dtype: object

In [173]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[173]: 
0     object
1    float64
2     object
3    float64
dtype: object

20 索引

20.1 文件隐式索引列

考虑下面这个文件,表头的数量比数据列的数量少一个

In [174]: print(open("foo.csv").read())
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5

在这种情况下,read_csv 假定第一列数据将作为 DataFrame 的索引

In [175]: pd.read_csv("foo.csv")
Out[175]: 
          A  B  C
20090101  a  1  2
20090102  b  3  4
20090103  c  4  5

注意: 在这种情况下,日期不会自动解析,你需要像以前一样指定参数

In [176]: df = pd.read_csv("foo.csv", parse_dates=True)

In [177]: df.index
Out[177]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)
20.2 多级索引

假设您有两列索引的数据

In [178]: print(open("data/mindex_ex.csv").read())
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5
1977,"C",1.7,.8
1978,"A",.2,.06
1978,"B",.7,.2
1978,"C",.8,.3
1978,"D",.9,.5
1978,"E",1.4,.9
1979,"C",.2,.15
1979,"D",.14,.05
1979,"E",.5,.15
1979,"F",1.2,.5
1979,"G",3.4,1.9
1979,"H",5.4,2.7
1979,"I",6.4,1.2

可以使用 index_col 参数传递一个列索引列表,以将多个列组合为多重索引

In [179]: df = pd.read_csv("data/mindex_ex.csv", index_col=[0, 1])

In [180]: df
Out[180]: 
             zit   xit
year indiv            
1977 A      1.20  0.60
     B      1.50  0.50
     C      1.70  0.80
1978 A      0.20  0.06
     B      0.70  0.20
     C      0.80  0.30
     D      0.90  0.50
     E      1.40  0.90
1979 C      0.20  0.15
     D      0.14  0.05
     E      0.50  0.15
     F      1.20  0.50
     G      3.40  1.90
     H      5.40  2.70
     I      6.40  1.20

In [181]: df.loc[1978]
Out[181]: 
       zit   xit
indiv           
A      0.2  0.06
B      0.7  0.20
C      0.8  0.30
D      0.9  0.50
E      1.4  0.90
20.3 列多级索引

通过为 header 参数指定行号列表,您可以将列表中读取的行作为列的多级索引。

如果指定了非连续的行,将会跳过中间的行

In [182]: from pandas._testing import makeCustomDataframe as mkdf

In [183]: df = mkdf(5, 3, r_idx_nlevels=2, c_idx_nlevels=4)

In [184]: df.to_csv("mi.csv")

In [185]: print(open("mi.csv").read())
C0,,C_l0_g0,C_l0_g1,C_l0_g2
C1,,C_l1_g0,C_l1_g1,C_l1_g2
C2,,C_l2_g0,C_l2_g1,C_l2_g2
C3,,C_l3_g0,C_l3_g1,C_l3_g2
R0,R1,,,
R_l0_g0,R_l1_g0,R0C0,R0C1,R0C2
R_l0_g1,R_l1_g1,R1C0,R1C1,R1C2
R_l0_g2,R_l1_g2,R2C0,R2C1,R2C2
R_l0_g3,R_l1_g3,R3C0,R3C1,R3C2
R_l0_g4,R_l1_g4,R4C0,R4C1,R4C2


In [186]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[186]: 
C0              C_l0_g0 C_l0_g1 C_l0_g2
C1              C_l1_g0 C_l1_g1 C_l1_g2
C2              C_l2_g0 C_l2_g1 C_l2_g2
C3              C_l3_g0 C_l3_g1 C_l3_g2
R0      R1                             
R_l0_g0 R_l1_g0    R0C0    R0C1    R0C2
R_l0_g1 R_l1_g1    R1C0    R1C1    R1C2
R_l0_g2 R_l1_g2    R2C0    R2C1    R2C2
R_l0_g3 R_l1_g3    R3C0    R3C1    R3C2
R_l0_g4 R_l1_g4    R4C0    R4C1    R4C2

read_csv 还能够解析一种更常见的多列索引格式

,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12

In [188]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[188]: 
     a         b   c    
     q  r  s   t   u   v
one  1  2  3   4   5   6
two  7  8  9  10  11  12

注意: 如果没有指定 index_col (例如,数据没有索引),那么列索引上的列名都会丢失

21 自动嗅探分隔符

read_csv 能够推断出文件的分隔符,因为 pandas 使用了 csv 模块的 csv.Sniffer 类。为此,你必须指定 sep=None

In [189]: print(open("tmp2.sv").read())
:0:1:2:3
0:0.4691122999071863:-0.2828633443286633:-1.5090585031735124:-1.1356323710171934
1:1.2121120250208506:-0.17321464905330858:0.11920871129693428:-1.0442359662799567
2:-0.8618489633477999:-2.1045692188948086:-0.4949292740687813:1.071803807037338
3:0.7215551622443669:-0.7067711336300845:-1.0395749851146963:0.27185988554282986
4:-0.42497232978883753:0.567020349793672:0.27623201927771873:-1.0874006912859915
5:-0.6736897080883706:0.1136484096888855:-1.4784265524372235:0.5249876671147047
6:0.4047052186802365:0.5770459859204836:-1.7150020161146375:-1.0392684835147725
7:-0.3706468582364464:-1.1578922506419993:-1.344311812731667:0.8448851414248841
8:1.0757697837155533:-0.10904997528022223:1.6435630703622064:-1.4693879595399115
9:0.35702056413309086:-0.6746001037299882:-1.776903716971867:-0.9689138124473498


In [190]: pd.read_csv("tmp2.sv", sep=None, engine="python")
Out[190]: 
   Unnamed: 0         0         1         2         3
0           0  0.469112 -0.282863 -1.509059 -1.135632
1           1  1.212112 -0.173215  0.119209 -1.044236
2           2 -0.861849 -2.104569 -0.494929  1.071804
3           3  0.721555 -0.706771 -1.039575  0.271860
4           4 -0.424972  0.567020  0.276232 -1.087401
5           5 -0.673690  0.113648 -1.478427  0.524988
6           6  0.404705  0.577046 -1.715002 -1.039268
7           7 -0.370647 -1.157892 -1.344312  0.844885
8           8  1.075770 -0.109050  1.643563 -1.469388
9           9  0.357021 -0.674600 -1.776904 -0.968914

22 逐块地遍历文件

假设你希望惰性地遍历一个(可能非常大的)文件,而不是一次性将整个文件读入内存

例如,有如下文件

In [191]: print(open("tmp.sv").read())
|0|1|2|3
0|0.4691122999071863|-0.2828633443286633|-1.5090585031735124|-1.1356323710171934
1|1.2121120250208506|-0.17321464905330858|0.11920871129693428|-1.0442359662799567
2|-0.8618489633477999|-2.1045692188948086|-0.4949292740687813|1.071803807037338
3|0.7215551622443669|-0.7067711336300845|-1.0395749851146963|0.27185988554282986
4|-0.42497232978883753|0.567020349793672|0.27623201927771873|-1.0874006912859915
5|-0.6736897080883706|0.1136484096888855|-1.4784265524372235|0.5249876671147047
6|0.4047052186802365|0.5770459859204836|-1.7150020161146375|-1.0392684835147725
7|-0.3706468582364464|-1.1578922506419993|-1.344311812731667|0.8448851414248841
8|1.0757697837155533|-0.10904997528022223|1.6435630703622064|-1.4693879595399115
9|0.35702056413309086|-0.6746001037299882|-1.776903716971867|-0.9689138124473498


In [192]: table = pd.read_csv("tmp.sv", sep="|")

In [193]: table
Out[193]: 
   Unnamed: 0         0         1         2         3
0           0  0.469112 -0.282863 -1.509059 -1.135632
1           1  1.212112 -0.173215  0.119209 -1.044236
2           2 -0.861849 -2.104569 -0.494929  1.071804
3           3  0.721555 -0.706771 -1.039575  0.271860
4           4 -0.424972  0.567020  0.276232 -1.087401
5           5 -0.673690  0.113648 -1.478427  0.524988
6           6  0.404705  0.577046 -1.715002 -1.039268
7           7 -0.370647 -1.157892 -1.344312  0.844885
8           8  1.075770 -0.109050  1.643563 -1.469388
9           9  0.357021 -0.674600 -1.776904 -0.968914

通过为 read_csv 指定 chunksize,将会返回 TextFileReader 类型的可迭代对象:

In [194]: with pd.read_csv("tmp.sv", sep="|", chunksize=4) as reader:
   .....:     reader
   .....:     for chunk in reader:
   .....:         print(chunk)
   .....: 
   Unnamed: 0         0         1         2         3
0           0  0.469112 -0.282863 -1.509059 -1.135632
1           1  1.212112 -0.173215  0.119209 -1.044236
2           2 -0.861849 -2.104569 -0.494929  1.071804
3           3  0.721555 -0.706771 -1.039575  0.271860
   Unnamed: 0         0         1         2         3
4           4 -0.424972  0.567020  0.276232 -1.087401
5           5 -0.673690  0.113648 -1.478427  0.524988
6           6  0.404705  0.577046 -1.715002 -1.039268
7           7 -0.370647 -1.157892 -1.344312  0.844885
   Unnamed: 0         0        1         2         3
8           8  1.075770 -0.10905  1.643563 -1.469388
9           9  0.357021 -0.67460 -1.776904 -0.968914

指定 iterator=True 可以返回 TextFileReader 对象

In [195]: with pd.read_csv("tmp.sv", sep="|", iterator=True) as reader:
   .....:     reader.get_chunk(5)

23 指定解析引擎

pandas 有两个解析器:

  • C 语言实现的快速高效的解析器
  • Python 实现的功能更加完善的解析器

pandas 尽可能使用 C 解析器,但是如果指定了 C 不支持的选项,将会使用 Python 解析器。

C 不支持的选项包括:

  • sep:除了单字符之外,如,正则表达式
  • skipfooter
  • sep=None 且 delim_whitespace=False

除非使用 engine ='python' 明确选择 python 引擎,否则指定以上任何选项都将产生 ParserWarning

24 远程文件读写

您可以传入一个 URL 来读取或写入远程文件,以下示例显示读取 CSV 文件:

df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")

25 数据写出

25.1 写出 CSV 格式

SeriesDataFrame 对象都有 to_csv 方法,该方法允许将对象的内容存储为逗号分隔文件。

该函数带有多个参数。只有第一个是必须的

  • path_or_buf: 要写入文件的路径或文件对象,如果是文件对象,必须使用 newline=''
  • sep : 输出文件的字段分隔符 (默认为 ,)
  • na_rep: 缺失值的字符串表示形式(默认为 '')
  • float_format: 浮点数的格式化字符串
  • columns: 要写入的列 (默认为 None)
  • header: 是否写出列名 (默认 True)
  • index: 是否写出索引名 (默认 True)
  • index_label: 索引列的列名,默认为 None,并且 headerindexTrue,则使用索引名称。如果 DataFrame 使用 MultiIndex,则应给出一个序列
  • mode : Python 写入模式,默认 'w'
  • encoding: 字符串编码格式
  • line_terminator: 表示行尾的字符序列(默认 os.linesep)
  • quoting: 在 csv 模块中设置引用规则(默认为 csv.QUOTE_MINIMAL)。注意:如果您设置了 float_format,那么浮点数将被转换为字符串,csv.QUOTE_NONNUMERIC 将它们视为非数字
  • quotechar: 用于引用字段的字符(默认 ")
  • doublequote: 控制字段中 quotechar 的引用 (默认 True)
  • escapechar: 在适当时用于转义 sepquotechar 的字符(默认 None)
  • chunksize: 一次写入的行数
  • date_format: datetime 对象的格式化字符串
25.2 写出格式化字符串

DataFrame 对象具有 to_string 实例方法,该方法允许控制对象的字符串表示形式。所有参数都是可选的

  • buf: 默认为 None,例如一个 StringIO 对象
  • columns: 默认为 None, 要写入的列
  • col_space: 默认为 None,每列的最小宽度。
  • na_rep: 默认为 NaN, NA 值的表示
  • formatters: 默认为 None, 一个函数的字典(按列),每个函数接受一个参数并返回一个格式化的字符串。
  • float_format: 默认为 None, 一个带有单个(浮点)参数并返回格式化字符串的函数;应用于 DataFrame 中的浮点数
  • sparsify: 默认为 True, 对于具有层次结构索引的 DataFrame,将其设置为 False 可在每一行打印每个 MultiIndex
  • index_names: 默认为 True, 将打印索引名
  • index: 默认为 True, 打印索引
  • header: 默认为 True, 打印列名
  • justify: 默认 left, 列左对齐或右对齐

Series 对象也有一个 to_string 方法,但是只有 bufna_repfloat_format 参数。

还有一个 length 参数,如果设置为 True,将额外输出序列的长度

  • 20
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值