第四章:数据读取与写入(IO 操作)
🎯 目标:掌握从各种数据源读取数据和保存数据的方法,打通数据流通的任督二脉。
1.1 为什么需要 IO 操作?
1.1.1 数据在哪里?
在现实工作中,数据分散在各个角落:
#mermaid-svg-WRs3NRDVP7ilha2D{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WRs3NRDVP7ilha2D .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WRs3NRDVP7ilha2D .error-icon{fill:#552222;}#mermaid-svg-WRs3NRDVP7ilha2D .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WRs3NRDVP7ilha2D .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WRs3NRDVP7ilha2D .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WRs3NRDVP7ilha2D .marker.cross{stroke:#333333;}#mermaid-svg-WRs3NRDVP7ilha2D svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WRs3NRDVP7ilha2D p{margin:0;}#mermaid-svg-WRs3NRDVP7ilha2D .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WRs3NRDVP7ilha2D .cluster-label text{fill:#333;}#mermaid-svg-WRs3NRDVP7ilha2D .cluster-label span{color:#333;}#mermaid-svg-WRs3NRDVP7ilha2D .cluster-label span p{background-color:transparent;}#mermaid-svg-WRs3NRDVP7ilha2D .label text,#mermaid-svg-WRs3NRDVP7ilha2D span{fill:#333;color:#333;}#mermaid-svg-WRs3NRDVP7ilha2D .node rect,#mermaid-svg-WRs3NRDVP7ilha2D .node circle,#mermaid-svg-WRs3NRDVP7ilha2D .node ellipse,#mermaid-svg-WRs3NRDVP7ilha2D .node polygon,#mermaid-svg-WRs3NRDVP7ilha2D .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WRs3NRDVP7ilha2D .rough-node .label text,#mermaid-svg-WRs3NRDVP7ilha2D .node .label text,#mermaid-svg-WRs3NRDVP7ilha2D .image-shape .label,#mermaid-svg-WRs3NRDVP7ilha2D .icon-shape .label{text-anchor:middle;}#mermaid-svg-WRs3NRDVP7ilha2D .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WRs3NRDVP7ilha2D .rough-node .label,#mermaid-svg-WRs3NRDVP7ilha2D .node .label,#mermaid-svg-WRs3NRDVP7ilha2D .image-shape .label,#mermaid-svg-WRs3NRDVP7ilha2D .icon-shape .label{text-align:center;}#mermaid-svg-WRs3NRDVP7ilha2D .node.clickable{cursor:pointer;}#mermaid-svg-WRs3NRDVP7ilha2D .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WRs3NRDVP7ilha2D .arrowheadPath{fill:#333333;}#mermaid-svg-WRs3NRDVP7ilha2D .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WRs3NRDVP7ilha2D .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WRs3NRDVP7ilha2D .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WRs3NRDVP7ilha2D .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WRs3NRDVP7ilha2D .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WRs3NRDVP7ilha2D .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WRs3NRDVP7ilha2D .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WRs3NRDVP7ilha2D .cluster text{fill:#333;}#mermaid-svg-WRs3NRDVP7ilha2D .cluster span{color:#333;}#mermaid-svg-WRs3NRDVP7ilha2D div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WRs3NRDVP7ilha2D .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WRs3NRDVP7ilha2D rect.text{fill:none;stroke-width:0;}#mermaid-svg-WRs3NRDVP7ilha2D .icon-shape,#mermaid-svg-WRs3NRDVP7ilha2D .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WRs3NRDVP7ilha2D .icon-shape p,#mermaid-svg-WRs3NRDVP7ilha2D .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WRs3NRDVP7ilha2D .icon-shape .label rect,#mermaid-svg-WRs3NRDVP7ilha2D .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WRs3NRDVP7ilha2D .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WRs3NRDVP7ilha2D .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WRs3NRDVP7ilha2D :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 你的分析任务
CSV 文件
Excel 表格
数据库
JSON API
Parquet 文件
Pandas DataFrame
分析结果
保存到文件
写入数据库
生成报告
IO 操作 = 数据的"进口"和"出口"
1.2 CSV 文件操作
1.2.1 读取 CSV(read_csv)
CSV(Comma-Separated Values)是最常见的数据格式。
python
import pandas as pd
# 🌟 基础读取
df = pd.read_csv('data.csv')
# 🌟 指定编码(处理中文乱码)
df = pd.read_csv('data.csv', encoding='utf-8') # UTF-8 编码
df = pd.read_csv('data.csv', encoding='gbk') # 中文 Windows 常用
df = pd.read_csv('data.csv', encoding='gb2312') # 国标编码
# 🌟 指定分隔符(默认是逗号)
df = pd.read_csv('data.txt', sep='\t') # Tab 分隔
df = pd.read_csv('data.csv', sep=';') # 分号分隔
df = pd.read_csv('data.csv', sep='|') # 竖线分隔
# 🌟 指定列名(如果文件没有表头)
df = pd.read_csv('data.csv', header=None) # 没有表头
df = pd.read_csv('data.csv', header=None, names=['姓名', '年龄', '城市']) # 自定义列名
# 🌟 指定索引列
df = pd.read_csv('data.csv', index_col=0) # 第1列作为索引
df = pd.read_csv('data.csv', index_col='姓名') # 指定列名作为索引
df = pd.read_csv('data.csv', index_col=['部门', '姓名']) # 多级索引
# 🌟 只读取部分列
df = pd.read_csv('data.csv', usecols=['姓名', '年龄', '月薪'])
df = pd.read_csv('data.csv', usecols=[0, 2, 4]) # 按位置选择列
# 🌟 只读取前 N 行
df = pd.read_csv('data.csv', nrows=1000)
# 🌟 跳过前 N 行
df = pd.read_csv('data.csv', skiprows=5)
# 🌟 处理大文件(分块读取)
chunk_size = 10000
for chunk in pd.read_csv('big_data.csv', chunksize=chunk_size):
# 每次处理 10000 行
print(f"处理 {len(chunk)} 行数据")
# 在这里进行数据处理...
1.2.2 写入 CSV(to_csv)
python
# 🌟 基础写入
df.to_csv('output.csv', index=False) # 不保存行索引
# 🌟 完整参数
df.to_csv(
'output.csv',
index=False, # 不保存行索引
encoding='utf-8', # 编码
sep=',', # 分隔符
na_rep='NULL', # 缺失值显示为 NULL
float_format='%.2f', # 浮点数保留2位小数
columns=['姓名', '年龄'], # 只保存指定列
header=True, # 保存列名
mode='w' # 写入模式('w'覆盖,'a'追加)
)
# 🌟 追加到已有文件
df_new.to_csv('output.csv', mode='a', header=False, index=False)
1.2.3 CSV 最佳实践
python
# ✅ 推荐:读取时处理好编码和索引
df = pd.read_csv(
'data.csv',
encoding='utf-8',
index_col='id',
parse_dates=['日期'], # 自动解析日期列
dtype={'类别': 'category'} # 指定列的数据类型
)
# ✅ 推荐:写入时不保存索引,避免重复列
df.to_csv('output.csv', index=False, encoding='utf-8-sig') # utf-8-sig 让 Excel 正确识别中文
1.3 Excel 文件操作
1.3.1 读取 Excel(read_excel)
python
# 🌟 基础读取(默认读取第一个 Sheet)
df = pd.read_excel('data.xlsx')
# 🌟 读取指定 Sheet
df = pd.read_excel('data.xlsx', sheet_name='Sheet1') # 按名称
df = pd.read_excel('data.xlsx', sheet_name=0) # 按位置(从0开始)
df = pd.read_excel('data.xlsx', sheet_name=['Sheet1', 'Sheet2']) # 读取多个 Sheet
# 🌟 读取所有 Sheet(返回字典)
all_sheets = pd.read_excel('data.xlsx', sheet_name=None)
print(all_sheets.keys()) # dict_keys(['Sheet1', 'Sheet2', 'Sheet3'])
df1 = all_sheets['Sheet1']
# 🌟 指定行作为表头
df = pd.read_excel('data.xlsx', header=0) # 第1行作为表头(默认)
df = pd.read_excel('data.xlsx', header=1) # 第2行作为表头
df = pd.read_excel('data.xlsx', header=None) # 没有表头
# 🌟 跳过行和指定列
df = pd.read_excel('data.xlsx', skiprows=2, usecols='A:C') # 跳过前2行,只读A-C列
df = pd.read_excel('data.xlsx', usecols=[0, 2, 4]) # 按位置选择列
# 🌟 指定数据类型
df = pd.read_excel('data.xlsx', dtype={'工号': str, '年龄': int})
1.3.2 写入 Excel(to_excel)
python
# 🌟 基础写入
df.to_excel('output.xlsx', index=False)
# 🌟 写入指定 Sheet
df.to_excel('output.xlsx', sheet_name='员工数据', index=False)
# 🌟 多个 DataFrame 写入不同 Sheet
with pd.ExcelWriter('output.xlsx') as writer:
df_employees.to_excel(writer, sheet_name='员工', index=False)
df_sales.to_excel(writer, sheet_name='销售', index=False)
df_products.to_excel(writer, sheet_name='产品', index=False)
# 🌟 追加到已有 Excel 文件(需要 openpyxl)
with pd.ExcelWriter('output.xlsx', mode='a', engine='openpyxl') as writer:
df_new.to_excel(writer, sheet_name='新数据', index=False)
1.3.3 Excel 引擎选择
| 引擎 | 读取 | 写入 | 特点 |
|---|---|---|---|
openpyxl |
✅ | ✅ | 支持 .xlsx,功能最全 |
xlrd |
✅ | ❌ | 只支持 .xls(旧格式) |
xlsxwriter |
❌ | ✅ | 写入功能强大,支持格式 |
python
# 指定引擎
df = pd.read_excel('data.xlsx', engine='openpyxl')
df.to_excel('output.xlsx', engine='xlsxwriter')
1.4 JSON 文件操作
1.4.1 读取 JSON(read_json)
JSON 是 Web API 最常用的数据格式。
python
# 🌟 从文件读取
df = pd.read_json('data.json')
# 🌟 从字符串读取
json_str = '''
[
{"姓名": "张三", "年龄": 25},
{"姓名": "李四", "年龄": 30}
]
'''
df = pd.read_json(json_str)
# 🌟 从 API 获取(配合 requests)
import requests
response = requests.get('https://api.example.com/data')
df = pd.read_json(response.text)
# 🌟 指定 orient(JSON 结构)
# orient='records': [{"col1": "a", "col2": "b"}, ...] 最常用
df = pd.read_json('data.json', orient='records')
# orient='index': {"row1": {"col1": "a", ...}, ...}
df = pd.read_json('data.json', orient='index')
# orient='columns': {"col1": {"row1": "a", ...}, ...}
df = pd.read_json('data.json', orient='columns')
1.4.2 写入 JSON(to_json)
python
# 🌟 基础写入
df.to_json('output.json')
# 🌟 指定 orient
df.to_json('output.json', orient='records', force_ascii=False) # force_ascii=False 保证中文正常显示
# 🌟 写入 JSON 字符串
json_str = df.to_json(orient='records', force_ascii=False)
print(json_str)
# [{"姓名":"张三","年龄":25}, {"姓名":"李四","年龄":30}]
1.5 SQL 数据库操作
1.5.1 连接数据库
python
from sqlalchemy import create_engine
# 🌟 MySQL
engine = create_engine('mysql+pymysql://用户名:密码@主机:端口/数据库名')
# 🌟 PostgreSQL
engine = create_engine('postgresql://用户名:密码@主机:端口/数据库名')
# 🌟 SQLite(本地文件数据库)
engine = create_engine('sqlite:///mydatabase.db')
# 🌟 SQL Server
engine = create_engine('mssql+pyodbc://用户名:密码@DSN名')
1.5.2 读取 SQL(read_sql)
python
# 🌟 读取整个表
df = pd.read_sql('SELECT * FROM employees', engine)
# 🌟 读取指定 SQL
df = pd.read_sql('''
SELECT 姓名, 部门, 月薪
FROM employees
WHERE 月薪 > 20000
ORDER BY 月薪 DESC
''', engine)
# 🌟 使用参数化查询(防止 SQL 注入)
df = pd.read_sql(
'SELECT * FROM employees WHERE 部门 = %s',
engine,
params=('技术',)
)
# 🌟 分块读取大数据
df_iter = pd.read_sql('SELECT * FROM big_table', engine, chunksize=10000)
for chunk in df_iter:
print(f"处理 {len(chunk)} 行")
1.5.3 写入 SQL(to_sql)
python
# 🌟 基础写入(如果表存在会报错)
df.to_sql('employees', engine, index=False)
# 🌟 如果表存在,替换
df.to_sql('employees', engine, index=False, if_exists='replace')
# 🌟 如果表存在,追加
df.to_sql('employees', engine, index=False, if_exists='append')
# 🌟 指定数据类型
df.to_sql(
'employees',
engine,
index=False,
dtype={
'姓名': sqlalchemy.types.VARCHAR(50),
'年龄': sqlalchemy.types.INTEGER(),
'月薪': sqlalchemy.types.DECIMAL(10, 2)
}
)
1.6 其他常用格式
1.6.1 Parquet 格式(大数据推荐)
python
# 🌟 读取 Parquet
df = pd.read_parquet('data.parquet')
# 🌟 写入 Parquet
df.to_parquet('output.parquet', index=False)
# 🌟 压缩(节省空间)
df.to_parquet('output.parquet', compression='gzip')
💡 Parquet 优势:列式存储,压缩率高,读写速度快,大数据生态标准格式。
1.6.2 HDF5 格式
python
# 🌟 读取 HDF5
df = pd.read_hdf('data.h5', key='df')
# 🌟 写入 HDF5
df.to_hdf('output.h5', key='df', mode='w')
# 🌟 追加到 HDF5
df.to_hdf('output.h5', key='df', mode='a')
1.6.3 剪贴板操作
python
# 🌟 从剪贴板读取(从 Excel 复制后直接读取)
df = pd.read_clipboard()
# 🌟 写入剪贴板(粘贴到 Excel)
df.to_clipboard(index=False)
1.6.4 格式对比
| 格式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CSV | 通用、可读 | 慢、占空间、无类型 | 小数据、交换 |
| Excel | 人人会用 | 慢、大小限制 | 报告、人工编辑 |
| JSON | Web 标准 | 占空间 | API、Web |
| Parquet | 快、压缩、有类型 | 不可读 | 大数据存储 |
| HDF5 | 快、可追加 | 依赖多 | 科学计算 |
| SQL | 可查询、事务 | 需要数据库 | 企业应用 |
1.7 实战案例:数据导入导出全流程
场景
你需要从多个数据源整合数据,处理后生成报告。
python
import pandas as pd
from sqlalchemy import create_engine
# 1. 从 CSV 读取销售数据
sales_df = pd.read_csv('sales_2024.csv', encoding='utf-8', parse_dates=['日期'])
print(f"销售数据:{len(sales_df)} 行")
# 2. 从 Excel 读取产品信息
product_df = pd.read_excel('products.xlsx', sheet_name='产品清单')
print(f"产品数据:{len(product_df)} 行")
# 3. 从数据库读取客户信息
engine = create_engine('sqlite:///crm.db')
customer_df = pd.read_sql('SELECT * FROM customers', engine)
print(f"客户数据:{len(customer_df)} 行")
# 4. 数据合并(后续章节详细讲解)
merged_df = sales_df.merge(product_df, on='产品ID').merge(customer_df, on='客户ID')
# 5. 数据处理
merged_df['销售额'] = merged_df['数量'] * merged_df['单价']
merged_df['月份'] = merged_df['日期'].dt.to_period('M')
# 6. 生成月度汇总报告
monthly_report = merged_df.groupby('月份').agg({
'销售额': 'sum',
'数量': 'sum',
'订单ID': 'count'
}).rename(columns={'订单ID': '订单数'})
# 7. 保存到多个格式
# 保存到 Excel(多个 Sheet)
with pd.ExcelWriter('月度报告.xlsx') as writer:
monthly_report.to_excel(writer, sheet_name='月度汇总')
merged_df.to_excel(writer, sheet_name='详细数据', index=False)
# 保存到 CSV
monthly_report.to_csv('月度报告.csv', encoding='utf-8-sig')
# 保存到 Parquet(用于后续分析)
merged_df.to_parquet('销售数据.parquet', index=False)
# 保存到数据库
monthly_report.to_sql('monthly_report', engine, if_exists='replace')
print("✅ 报告生成完成!")
1.8 本章小结
核心要点
✅ CSV :read_csv() / to_csv() ------ 最通用,注意编码和分隔符
✅ Excel :read_excel() / to_excel() ------ 需要 openpyxl,注意 Sheet 选择
✅ JSON :read_json() / to_json() ------ Web 数据标准,注意 orient
✅ SQL :read_sql() / to_sql() ------ 需要 SQLAlchemy,注意连接字符串
✅ Parquet :read_parquet() / to_parquet() ------ 大数据推荐格式
✅ 最佳实践:
- 读取时指定
encoding='utf-8' - 写入时设置
index=False - 大文件使用
chunksize分块读取 - 日期列使用
parse_dates自动解析