3. 数据读取与初步探索:read_csv/read_excel、head/tail/info/describe、数据类型概览
好,咱们正式开始动手了。
前面两章聊了架构和工具链,说白了都是热身。这一章才是真正跟数据打交道的第一步——把文件读进来,然后快速摸清它的底细。
我个人习惯,拿到一个新数据集,第一件事不是跑模型,而是先看看它长什么样。就像你拿到一本新书,总得先翻翻目录、看看前言吧?数据探索就是这个道理。
3.1 读取文件:read_csv 与 read_excel
投研平台里最常见的数据源就是 CSV 和 Excel。Pandas 提供了两个非常顺手的函数:read_csv 和 read_excel。
先看 CSV 的读取:
import pandas as pd
# 最简单的读取
df = pd.read_csv('stock_data.csv')
# 带参数读取,我建议你养成这个习惯
df = pd.read_csv(
'stock_data.csv',
encoding='utf-8', # 避免乱码
parse_dates=['date'], # 日期列自动解析
dtype={'code': str}, # 股票代码强制为字符串
low_memory=False # 大文件时避免类型推断警告
)
这里有个坑,我曾经遇到过:某次从 Wind 导出的 CSV,股票代码列被 Excel 自动截断了前导零。比如 "000001" 变成了 "1"。所以 dtype={'code': str} 这行,我几乎每次都会写。
再看 Excel 读取:
# 读取第一个sheet
df = pd.read_excel('fund_data.xlsx', sheet_name=0)
# 读取指定sheet
df = pd.read_excel('fund_data.xlsx', sheet_name='持仓明细')
# 大Excel文件,建议用openpyxl引擎
df = pd.read_excel('large_file.xlsx', engine='openpyxl')
pd.ExcelFile('file.xlsx').sheet_names 先列出来。我每次接新数据源都会先跑这行,心里有个底。
3.2 初步探索:head / tail / info / describe
数据读进来了,接下来就是“看一眼”。Pandas 提供了四个最常用的方法,我管它们叫“四件套”。
3.2.1 head() 和 tail()
看头看尾,快速确认数据格式对不对。
# 看前5行(默认)
df.head()
# 看前10行
df.head(10)
# 看后5行
df.tail()
# 看后3行
df.tail(3)
为什么要看 tail?因为很多数据源会在文件末尾加汇总行或者空行。我遇到过好几次,最后一行是 "合计:1000条",如果不看 tail,直接做聚合计算,结果就偏了。
3.2.2 info() —— 数据集的“体检报告”
info() 是我用得最多的方法,没有之一。它告诉你:有多少行、多少列、每列的类型、有没有空值。
df.info()
输出大概长这样:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 10000 non-null datetime64[ns]
1 code 10000 non-null object
2 name 10000 non-null object
3 close 9995 non-null float64
4 volume 10000 non-null int64
5 amount 10000 non-null float64
6 pe_ttm 8000 non-null float64
7 pb 7500 non-null float64
dtypes: datetime64[ns](1), float64(4), int64(1), object(2)
memory usage: 625.0+ KB
看到没?close 列有 5 个空值,pe_ttm 和 pb 缺失更多。这就是下一步清洗的信号。
3.2.3 describe() —— 数值列的统计摘要
describe() 自动对数值列做统计:计数、均值、标准差、四分位数等。
df.describe()
输出示例:
| close | volume | amount | pe_ttm | pb | |
|---|---|---|---|---|---|
| count | 9995.0 | 10000.0 | 10000.0 | 8000.0 | 7500.0 |
| mean | 25.36 | 1.2e7 | 3.1e8 | 18.5 | 2.1 |
| std | 12.04 | 8.5e6 | 2.2e8 | 15.2 | 1.8 |
| min | 1.02 | 0 | 0 | -50.3 | 0.1 |
| 25% | 15.80 | 5.2e6 | 1.1e8 | 8.2 | 0.9 |
| 50% | 22.10 | 1.0e7 | 2.5e8 | 15.0 | 1.6 |
| 75% | 32.50 | 1.8e7 | 4.5e8 | 25.3 | 2.8 |
| max | 98.76 | 9.9e7 | 2.1e9 | 200.0 | 15.0 |
这里要注意几个点:
- count 不一致:说明有缺失值,跟 info() 对得上
- min 出现负数:比如 pe_ttm 最小值是 -50.3,这可能是亏损公司的数据,也可能是异常值
- max 异常大:pe_ttm 最大值 200,明显偏离正常范围,需要进一步排查
df['column'].value_counts()。我曾经因为没检查分类列,结果发现 "行业" 列里混入了 "行业" 这个表头本身,整整多了一行脏数据。
3.3 数据类型概览:dtypes 与 astype
数据类型是数据清洗的基石。Pandas 里常见的数据类型有:
- int64:整数,比如成交量
- float64:浮点数,比如股价、市盈率
- object:字符串/混合类型,比如股票代码、股票名称
- datetime64:日期时间,比如交易日期
- bool:布尔值,比如是否ST
查看所有列的类型:
df.dtypes
如果需要转换类型,用 astype:
# 把股票代码转为字符串
df['code'] = df['code'].astype(str)
# 把日期列转为datetime
df['date'] = pd.to_datetime(df['date'])
# 把市盈率转为浮点数(如果混入了文本,会报错)
df['pe_ttm'] = pd.to_numeric(df['pe_ttm'], errors='coerce')
这里 errors='coerce' 是个好参数。它会把无法转换的值变成 NaN,而不是直接报错。我处理投研数据时,经常遇到市盈率列里混入了 "--" 或者 "N/A",用这个参数就能优雅地处理。
df.dtypes 扫一遍。如果发现本该是数字的列显示为 object,那八成是数据里有脏值。这时候别急着转类型,先用 df[df['列名'].str.contains('[a-zA-Z]', na=False)] 把异常行揪出来看看。
3.4 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作数据探索的“操作手册”。
这张图其实就概括了本章的全部内容。你每次拿到新数据,都可以按这个流程走一遍。我自己的习惯是:先读进来,看头看尾,跑 info 和 describe,然后检查 dtypes。这一套下来,数据的基本情况就摸得七七八八了。
嗯,数据探索这一步,说白了就是“知己知彼”。你只有知道数据长什么样、有什么坑,后面的清洗才能有的放矢。别急着往下跳,多花几分钟做探索,后面能省几小时。