1-1 Series与DataFrame:建第一份体检档案 本节摘要:Series是一维带标签数组,DataFrame是共享同一套行索引的多个Series按列拼成的二维表。本节在体检流水线的位置:这是接诊建档的第一步,后面所有科室都在这两种档案上进行操作。 承接本章主线:病人进门前台,第一件事是认清档案袋。1-1建档案,1-2挂号,1-3登记,1-4归档。 化验单视角看Series 一张血常规化验单上,"白细胞计数"这一项有名称、有数值、有参考区间。Pandas里的Series就是"一项指标的一串历史测量值":数据本身是一维数组,每条测量值带一个标签(测量日期),这一串标签就是索引。 values给出"剥离标签后的裸数据",index给出挂号条。
本节摘要:Series是一维带标签数组,DataFrame是共享同一套行索引的多个Series按列拼成的二维表。本节在体检流水线的位置:这是接诊建档的第一步,后面所有科室都在这两种档案上进行操作。
承接本章主线:病人进门前台,第一件事是认清档案袋。1-1建档案,1-2挂号,1-3登记,1-4归档。
一张血常规化验单上,"白细胞计数"这一项有名称、有数值、有参考区间。Pandas里的Series就是"一项指标的一串历史测量值":数据本身是一维数组,每条测量值带一个标签(测量日期),这一串标签就是索引。
import pandas as pd # 用列表构造一个Series:三家门店一月份的销售额 s = pd.Series([128000, 96500, 173200], index=['门店A', '门店B', '门店C']) print(s) # 门店A 128000 # 门店B 96500 # 门店C 173200 # dtype: int64 print(s.values) # 取出纯数值:[128000 96500 173200] print(s.index) # Index(['门店A', '门店B', '门店C'], dtype='object') print(s.mean()) # 132566.66666666667
values给出"剥离标签后的裸数据",index给出挂号条。新手的第一个常见误会就在这里:Series不是"带行号的列表",行号只是索引的一种特例,标签完全可以是日期、门店名、甚至多级元组。
Series支持向量化运算,这是Pandas全家(后面第八章的性能话题)的根基:
# 全部门店打九折促销后的销售额,一行完成,无需循环 print(s * 0.9) # 门店A 115200.0 # 门店B 86850.0 # 门店C 155880.0 # dtype: float64 # 布尔筛选:销售额超过10万的门店 print(s[s > 100000]) # 门店A 128000 # 门店C 173200 # dtype: int64
一本项目病历册,左页是日期(所有指标共用),右页起每一列是一个指标。DataFrame就是这个结构:一套行索引,若干列各自成Series,列名是字段名。构造它最自然的方式是字典——键做列名,值做列数据。
# 月度销售明细:我们的主线"病人"第一次亮相 df = pd.DataFrame({ '日期': ['2024-01-03', '2024-01-03', '2024-01-10', '2024-01-15', '2024-01-21', '2024-01-21'], '门店': ['A', 'B', 'A', 'C', 'A', 'A'], '金额': [1280, 960, 1732, '880', 2100, 2100], # 混进了一个字符串 }) print(df) # 日期 门店 金额 # 0 2024-01-03 A 1280 # 1 2024-01-03 B 960 # 2 2024-01-10 A 1732 # 3 2024-01-15 C 880 # 4 2024-01-21 A 2100 # 5 2024-01-21 A 2100 print(df.shape) # (6, 3) —— 6次交易、3个字段 print(df.dtypes) # 每列一份"血型鉴定" # 日期 object # 门店 object # 金额 object # 混入字符串后整列被降级为object,这是第一章埋下的病灶
注意dtypes的输出:金额列本该是数字,因为第4行混进了字符串'880',整列被拖成了object。这种"一粒老鼠屎坏一锅汤"的类型传染,是第四章专科门诊要正式处理的病,这里先建档留底。
列存取、取行、改值,三件事在接诊阶段就要形成肌肉记忆:
# 取一列:得到Series print(df['金额']) # 取多列:得到新DataFrame print(df[['日期', '金额']].head(2)) # 用loc按标签取一行 print(df.loc[2, '金额']) # 1732 # 新增一列:全部门店金额换算成万元 df['金额万元'] = None # 先占位,下一节会用更规范的方式填充 print(df.columns) # Index(['日期', '门店', '金额', '金额万元'], dtype='object')
⚠️ 常见坑:
df['金额']返回的是视图还是副本的旧争论已过时,现代Pandas会对链式赋值发出SettingWithCopyWarning。看到这个警告,说明你的"修改"可能写在一份数据的副本上——正确做法是先显式复制,或用单次.loc完成读写。
💡 关键直觉:DataFrame不是"行的集合"而是"列的订册"。理解了列式思维,向量化、groupby、merge的很多行为都会顺理成章。
数据再大一点,逐列打印就不现实了。接诊阶段还有两件总览器械值得形成习惯:
# info:规模、稀疏度、血型一份看全 df.info() # describe:数值列的五数概括加均值 print(df['金额'].describe())
describe此刻只输出一行有效信息(金额列还是object血型时甚至不出现在describe里),但等第四章转完类型,它会立刻变成体检查房的常客。这种"同一个函数在不同健康阶段输出不同"的现象,本身就是数据状态的信号:describe里缺席的数值列,几乎都有血型问题。
拿到任何新数据,固定做四个动作:head看样本、shape看规模、dtypes验血型、info看稀疏。四个动作十秒钟,后面每一步操作都有依据。反过来的代价是把类型病带进聚合,在报告里才发现数字对不上——那时回溯的成本是十秒的一百倍。
下一节给这份档案挂上正式的号——Index,它是Pandas对齐机制的身份证。