1-3 多源数据的接诊登记:读取 本节摘要:readcsv与readexcel是登记窗口的主力,本节围绕真实文件的常见症状——中文乱码、怪分隔符、说明行、类型误判——讲清读取参数怎么开,让数据在进门那一刻就完成第一跳体检。 档案建好、号也挂了(前两节),现在打开登记窗口。数据的来路五花八门:同事发来的CSV、财务的Excel多表、数据库里的正式记录。登记阶段多花三分钟设参数,后面各科室能少踩一半坑。 基础登记与进门第一跳体检 head看到的第一行应该是列名。如果文件根本没有表头,readcsv默认会把第一行数据当列名吞掉,需要显式声明: 中文乱码与分隔符 ⚠️ 常见坑:编码问题没有万能参数。先看报错信息里的failed字节位置,用二进制模式读前两百字节判断,比连环猜编码快得多。
本节摘要:read_csv与read_excel是登记窗口的主力,本节围绕真实文件的常见症状——中文乱码、怪分隔符、说明行、类型误判——讲清读取参数怎么开,让数据在进门那一刻就完成第一跳体检。
档案建好、号也挂了(前两节),现在打开登记窗口。数据的来路五花八门:同事发来的CSV、财务的Excel多表、数据库里的正式记录。登记阶段多花三分钟设参数,后面各科室能少踩一半坑。
import pandas as pd # 最普通的登记 df = pd.read_csv('sales.csv') # 文件内容见下方说明 # 进门三件事:规模、样本、类型 print(df.shape) # (1000, 6) print(df.head(3)) # 看前三行,确认列名没被当成数据 print(df.dtypes) # 每列的血型鉴定
head看到的第一行应该是列名。如果文件根本没有表头,read_csv默认会把第一行数据当列名吞掉,需要显式声明:
# 无表头文件:header=None,再补列名 df = pd.read_csv('raw.csv', header=None, names=['日期', '门店', '金额', '渠道']) # 前几行是报表说明文字:skiprows跳过 df2 = pd.read_csv('report.csv', skiprows=2) # 表尾有合计行:skipfooter裁掉 df3 = pd.read_csv('report.csv', skiprows=2, skipfooter=1, engine='python')
# 症状:读老系统导出的CSV直接抛UnicodeDecodeError # 处置:换成GBK编码再登记 df = pd.read_csv('legacy.csv', encoding='gbk') # 症状:读进来的表只有一列,列名里挤着所有字段 # 诊断:分隔符是制表符或分号,不是逗号 df = pd.read_csv('export.txt', sep='\t') # 制表符 df = pd.read_csv('export_eu.csv', sep=';') # 欧式分号 # 症状:千分位逗号让金额列变成字符串 # 处置:thousands参数让登记员自动摘掉千分位 df = pd.read_csv('big.csv', thousands=',')
⚠️ 常见坑:编码问题没有万能参数。先看报错信息里的failed字节位置,用二进制模式读前两百字节判断,比连环猜编码快得多。
第四章会专门讲类型转科,但有两类问题最好在登记窗口就地解决:金额列被当成字符串、日期列被当成普通文本。
df = pd.read_csv( 'sales.csv', dtype={'门店': 'category', '金额': 'float64'}, # 指定列的血型 parse_dates=['日期'], # 日期直接挂特殊号 ) print(df.dtypes) # 日期 datetime64[ns] # 门店 category # 金额 float64 # 日期格式不标准时,单独声明格式,比让Pandas猜更稳 df['日期'] = pd.to_datetime(df['日期'], format='%Y年%m月%d日')
parse_dates进门就绪的价值要到第07章才完全显现——时间索引的随访、重采样、移动窗口全部建立在datetime64类型之上。
# Excel:一个工作簿多个科室各自的表 sheets = pd.read_excel('台账.xlsx', sheet_name=None) # None = 全部读成字典 print(list(sheets.keys())) # ['一月', '二月', '汇总'] df_all = pd.concat(sheets.values(), ignore_index=True) # 病历叠加,第06章展开 # 数据库:用SQL当挂号单,只把需要的记录叫进来 from sqlalchemy import create_engine engine = create_engine('sqlite:///biz.db') orders = pd.read_sql('SELECT 日期, 门店, 金额 FROM orders WHERE 金额 > 100', engine) print(orders.shape)
| 进门症状 | 大概率病因 | 登记参数 |
|---|---|---|
| 抛出解码错误 | 文件是GBK或GB18030 | encoding='gbk' |
| 全表挤成一列 | 分隔符非逗号 | sep='\t' 或 sep=';' |
| 金额列是object | 千分位逗号作祟 | thousands=',' |
| 首行是说明文字 | 报表带了抬头 | skiprows=2 |
| 日期列是字符串 | 未解析日期 | parse_dates与format |
| 数字串丢失前导零 | 被当成整数 | dtype={'编号':str} |
最后一行的前导零问题值得单独提:员工编号"007"进门后变7,报销系统对账时才发现。凡是有编码语义的列,登记时就该指定字符串血型。
登记窗口还会遇到体积问题:一个几GB的CSV,read_csv一把全读直接把内存打爆。第一招是nrows先探路,看前几千行确认口径再决定全量策略;第二招是usecols裁列,五十列只用五列时内存直接降一个量级;终极方案是chunksize分块,把文件切成生成器逐块处理,每块只带走聚合结果。这三招的完整展开在第08章,这里先把"读不进来不等于没办法"的意识建立起来。
# 探路:先看一万行,确认口径 peek = pd.read_csv('huge.csv', nrows=10000) print(peek.dtypes, peek.shape) # 裁列:只登记需要的字段 need = pd.read_csv('huge.csv', usecols=['日期', '门店', '金额'])
登记的原则始终如一:进内存的数据越少,流水线越稳。探路、裁列、分块,三个动作都是这个原则的具体化。
下一节讲出口:检查做完的报告怎么写出去、怎么归档不丢信息。