背景
pandas is an open source, BSD-licensed library providing high-performance, easy-to-use data structures and data analysis tools for the Python programming language.
数据分析工具包 pandas,是利用 Python 进行数据分析时,一个难以割舍的选项。它的官方文档 pandas/docs 示例非常丰富,而且它的作者 Wes McKinney 另外还写了一本书 Python for Data Analysis,截至目前已经更新到第 3 版,👆点击下载。
中国国内也有热心网友做了免费的翻译分享,这是第 2 版,简书-《Python数据分析》2nd。
我曾经实现过一个单机部署的 ETL (extract, transform, load) 程序,三个步骤都基于 pandas 实现。不过这个程序最常用的功能,却仅仅是定时读取一批 SQL,然后写入 Excel,最后把这些 Excel 文件作为邮件附件进行发送,真是杀鸡用牛刀😂。
其实,我个人并不太喜欢使用 Excel 文件,先不论 Excel 那缓慢的打开速度,它的一个工作表最多也只能有 1048576(2^20) 行和 16384(2^14) 列,工作表名字最多 31 个字符。所以在我看来,CSV 才是更好的选项。
在那个 ETL
中,只允许从每个源读取一个 pandas 的 DataFrame
,但在输出时,可以把多个 DataFrame
输出到一个目标里面。对于一种这类情况,即把多个 DataFrame
输出到同一个 Excel
工作簿,如果这个目标之后再作为一个源时,就不好处理了。如果这是最终的输出,而不是管道的一个中间环节,却是可以接受的。
Excel
最大的问题是工作表的规模有限,如果你的表格的规格超出 1048576×16384,也就是这个矩形不能把你数据表格完全盖住,你就得对 DataFrame
进行拆分。一般来说,我个人是不推荐做拆分的。我的建议是,一个工作簿,只开一个工作表,如果一个工作表存储不下,那就用 CSV
或者 hdf5 格式。
代码实现
TIPS 代码的最新版本在 GitHub Gist 中维护
https://gist.github.com/ChenyangGao/6a3f6177ce8da748413ce304156588f9
文件名称是 pandas_to_excel_bytes.py
,Python 实现代码如下:
1 | #!/usr/bin/env python3 |
扩展阅读
1. 把 pandas
的 DataFrame
导出各种编码格式的字符串
如果每个 pandas 的 DataFrame
,各自导出到不同的目标,那么可以运用策略模式,实现一个特别简单的统一处理函数,返回导出的 bytes
或 str
。
filetype | outtype | RETURN TYPE | |
---|---|---|---|
0 | 'csv' |
bytes | bytes |
1 | 'csv' |
str | str |
2 | 'csv' |
None | str |
3 | 'excel' |
bytes | bytes |
4 | 'excel' |
None | bytes |
5 | 'feather' |
bytes | bytes |
6 | 'feather' |
None | bytes |
7 | 'hdf' |
bytes | bytes |
8 | 'hdf' |
None | bytes |
9 | 'html' |
bytes | bytes |
10 | 'html' |
str | str |
11 | 'html' |
None | str |
12 | 'json' |
bytes | bytes |
13 | 'json' |
str | str |
14 | 'json' |
None | str |
15 | 'latex' |
bytes | bytes |
16 | 'latex' |
str | str |
17 | 'latex' |
None | str |
18 | 'markdown' |
bytes | bytes |
19 | 'markdown' |
str | str |
20 | 'markdown' |
None | str |
21 | 'parquet' |
bytes | bytes |
22 | 'parquet' |
None | bytes |
23 | 'pickle' |
bytes | bytes |
24 | 'pickle' |
None | bytes |
25 | 'stata' |
bytes | bytes |
26 | 'stata' |
None | bytes |
27 | 'string' |
bytes | bytes |
28 | 'string' |
str | str |
29 | 'string' |
None | str |
30 | 'xml' |
bytes | bytes |
31 | 'xml' |
str | str |
32 | 'xml' |
None | str |
1 | #!/usr/bin/env python3 |
2. 基于pandas实现一个简单的ETL程序
我用的 Python 和 pandas 版本如下
1 | $ python -V |
ETL
的 extract
这一步,需要封装所有 pandas.read_*
方法,罗列如下
1 | import pandas as pd |
读入方法 | 说明 | |
---|---|---|
0 | pandas.read_clipboard | Read text from clipboard and pass to read_csv. |
1 | pandas.read_csv | Read a comma-separated values (csv) file into DataFrame. |
2 | pandas.read_excel | Read an Excel file into a pandas DataFrame. |
3 | pandas.read_feather | Load a feather-format object from the file path. |
4 | pandas.read_fwf | Read a table of fixed-width formatted lines into DataFrame. |
5 | pandas.read_gbq | Load data from Google BigQuery. |
6 | pandas.read_hdf | Read from the store, close it if we opened it. |
7 | pandas.read_html | Read HTML tables into a list of DataFrame objects. |
8 | pandas.read_json | Convert a JSON string to pandas object. |
9 | pandas.read_orc | Load an ORC object from the file path, returning a DataFrame. |
10 | pandas.read_parquet | Load a parquet object from the file path, returning a DataFrame. |
11 | pandas.read_pickle | Load pickled pandas object (or any object) from file. |
12 | pandas.read_sas | Read SAS files stored as either XPORT or SAS7BDAT format files. |
13 | pandas.read_spss | Load an SPSS file from the file path, returning a DataFrame. |
14 | pandas.read_sql | Read SQL query or database table into a DataFrame. |
15 | pandas.read_sql_query | Read SQL query into a DataFrame. |
16 | pandas.read_sql_table | Read SQL database table into a DataFrame. |
17 | pandas.read_stata | Read Stata file into DataFrame. |
18 | pandas.read_table | Read general delimited file into DataFrame. |
19 | pandas.read_xml | Read XML document into a DataFrame object. |
而做 ETL
的 load
这一步,需要封装所有 pandas.DataFrame.to_*
方法,罗列如下
1 | import pandas as pd |
写出方法 | 说明 | |
---|---|---|
0 | pandas.DataFrame.to_clipboard | Copy object to the system clipboard. |
1 | pandas.DataFrame.to_csv | Write object to a comma-separated values (csv) file. |
2 | pandas.DataFrame.to_dict | Convert the DataFrame to a dictionary. |
3 | pandas.DataFrame.to_excel | Write object to an Excel sheet. |
4 | pandas.DataFrame.to_feather | Write a DataFrame to the binary Feather format. |
5 | pandas.DataFrame.to_gbq | Write a DataFrame to a Google BigQuery table. |
6 | pandas.DataFrame.to_hdf | Write the contained data to an HDF5 file using HDFStore. |
7 | pandas.DataFrame.to_html | Render a DataFrame as an HTML table. |
8 | pandas.DataFrame.to_json | Convert the object to a JSON string. |
9 | pandas.DataFrame.to_latex | Render object to a LaTeX tabular, longtable, or nested table/tabular. |
10 | pandas.DataFrame.to_markdown | Print DataFrame in Markdown-friendly format. |
11 | pandas.DataFrame.to_numpy | Convert the DataFrame to a NumPy array. |
12 | pandas.DataFrame.to_parquet | Write a DataFrame to the binary parquet format. |
13 | pandas.DataFrame.to_period | Convert DataFrame from DatetimeIndex to PeriodIndex. |
14 | pandas.DataFrame.to_pickle | Pickle (serialize) object to file. |
15 | pandas.DataFrame.to_records | Convert DataFrame to a NumPy record array. |
16 | pandas.DataFrame.to_sql | Write records stored in a DataFrame to a SQL database. |
17 | pandas.DataFrame.to_stata | Export DataFrame object to Stata dta format. |
18 | pandas.DataFrame.to_string | Render a DataFrame to a console-friendly tabular output. |
19 | pandas.DataFrame.to_timestamp | Cast to DatetimeIndex of timestamps, at beginning of period. |
20 | pandas.DataFrame.to_xarray | Return an xarray object from the pandas object. |
21 | pandas.DataFrame.to_xml | Render a DataFrame to an XML document. |
在 ETL
的三个步骤中,最重要的是 T
(transform)。我之前的项目,在 transform
这一步,专门开发了一个 DSL(Domain-specific language),会把一种相对简单的语言翻译成 Python 和 pandas 操作。但是作为示例来说,这过于复杂了,等下次分享如何开发脚本语言时,我再展开这个例子。
为了简化,我把 ETL
简化成如下这样一个上下文管理器
1 | from contextlib import contextmanager |
其中 read
和 write
在调用函数时传入,请确保传入的参数类型正确,必须是两个可调用对象,如果它们各自还有其它参数,请提前用偏函数 functools.partial 进行捆绑。
未完待续