在数据分析的世界里,pandas.DataFrame
是最常用的数据结构之一。然而,许多初学者甚至中级用户都会遇到一个常见的困惑:为什么在构造DataFrame时,有时候 Series
变成一列,有时候却变成一行?为什么Pandas的Series底层由NumPy数组提供支撑,但是NumPy ndarray和 Pandas DataFrame在行和列的定义上有时候看起来是“反过来的”?
本文将系统梳理 DataFrame
的构造逻辑,揭示其背后的设计哲学,并强调一个至关重要的概念:样本 vs. 特征。掌握这些原则,你将不再被“按行还是按列”所困扰。
一、核心概念:样本 vs. 特征 —— 符合人类直觉的表格设计
在数据分析中,样本(Sample) 和 特征(Feature) 的区别是理解表格结构的基础。
核心概念:
概念 | 含义 | 在表格中的位置 |
---|---|---|
样本(Sample) | 一个独立的观测或实例(如一个人、一次实验) | 一行 |
特征(Feature) | 描述样本的属性或变量(如身高、体重、温度) | 一列 |
为什么这样设计符合人类直觉?
想象你有一张 Excel 表:
身高(cm) | 体重(kg) | 年龄 | |
---|---|---|---|
张三 | 175 | 70 | 25 |
李四 | 168 | 55 | 30 |
王五 | 180 | 80 | 22 |
- 每一行是一个人(样本)
- 每一列是一个属性(特征)
这正是 DataFrame
的设计哲学:行是样本,列是特征。
现实世界中的普遍实践
这种设计不仅符合直觉,更是现实工作流的体现:
- Excel 中的数据录入:当你收集新数据时,通常是在表格底部追加一行(如新增一个员工、一次销售记录),而不是在右侧追加一列。列名(如“姓名”、“部门”、“薪资”)一旦定义,通常不会轻易变动,它们代表了数据的结构和模式。
- 关系型数据库(如 MySQL):在数据库表中,每一行代表一条记录(record),每一列代表一个字段(field)。新增数据意味着插入一条新记录(一行),而修改表结构(如添加新字段)是相对少见且需要谨慎操作的变更。
这说明:“列是稳定的结构,行是动态的数据” 是一种广泛接受的数据组织范式。
Series
本身没有“行/列”方向
在代码中,因为我们只能“横着写”数据,所以并不清楚Series到底是行还是列:
s = pd.Series([1, 2, 3])
这个 Series
本身既不是行也不是列——它只是一个一维带标签的数组。
Series
的“方向”(是行还是列)完全取决于它被如何使用
- 当你把它放进
{'A': s}
这样的字典里 → Pandas 认为它是一个特征 → 成为一列 - 当你把它放进
[s1, s2]
这样的列表里 → Pandas 认为它是一个样本 → 成为一行
二、核心原则:输入结构决定组织方式
Pandas 在构造 DataFrame
时,并不是随意决定数据如何排列的。它会根据你传入的数据结构,自动推断数据的“语义角色”。这个过程遵循一个简单而强大的原则:
“字典是列,列表是行”
让我们通过几个典型场景来理解这一点。
三、场景一:用字典构造 —— 按“列”组织(特征优先)
当你使用 字典(dict
) 构造 DataFrame
时,Pandas 会认为你是在定义多个“变量”或“特征”。
情况 1:{'列名': Series}
import pandas as pd
s1 = pd.Series([1, 2, 3])
s2 = pd.Series([4, 5, 6])
df = pd.DataFrame({'A': s1, 'B': s2})
结果:
A B
0 1 4
1 2 5
2 3 6
'A'
,'B'
是列名s1
→ A 列,s2
→ B 列- 每个
Series
成为一列
语义:你有两个变量 A 和 B,每个变量是一组观测值(即两个特征)。
情况 2:{'列名': 字典}
data = {
'A': {'a': 1, 'b': 4},
'B': {'a': 2, 'b': 5}
}
df = pd.DataFrame(data)
结果:
A B
a 1 2
b 4 5
- 外层键
'A'
,'B'
→ 列名(特征名) - 内层键
'a'
,'b'
→ 行索引(样本标签) - 自动对齐,缺失值填
NaN
这是最常见的“列优先”构造方式,完全符合“列是特征”的直觉。
自动对齐与 NaN 填充
DataFrame
的核心是一个规则的二维矩形结构。当使用字典构造时,如果不同列的行索引不完全一致,Pandas 会自动将所有列的索引并集作为最终的行索引,并在缺失位置填充 NaN
,确保结构完整。
例如:
data2 = {
'A': {'a': 1, 'b': 4},
'B': {'a': 2, 'b': 5},
'C': {'a': 3, 'c': 6} # 'C' 缺少 'b',但有 'c'
}
df = pd.DataFrame(data2)
结果:
A B C
a 1.0 2.0 3.0
b 4.0 5.0 NaN
c NaN NaN 6.0
Pandas 会自动对齐索引,用
NaN
填补空缺,形成一个完整的矩形网格,让你无需手动处理不完整数据。
四、场景二:用列表构造 —— 按“行”组织(样本优先)
当你使用 列表(list
) 构造 DataFrame
时,Pandas 会认为你是在记录多个“样本”或“观测”。
情况 1:[Series, Series]
df = pd.DataFrame([s1, s2])
结果:
0 1 2
0 1 2 3
1 4 5 6
s1
→ 第 0 行(样本1),s2
→ 第 1 行(样本2)s1
的索引[0,1,2]
→ 成为列名(特征名)- 每个
Series
成为一行(一个样本)
语义:你有两个样本,每个样本有三个特征。
情况 2:[列表, 列表]
df = pd.DataFrame([
[1, 2, 3],
[4, 5, 6]
])
结果同上。
列表的每个元素(子列表)成为一行(一个样本)。
列表构造的“规则矩形”特性:处理不等长的 Series
当列表中的 Series
长度不一致时,Pandas 会以所有 Series
索引的并集作为最终的列索引,并用 NaN
填充缺失值,确保 DataFrame
保持规则的矩形结构。
s1 = pd.Series([1, 2, 3]) # 索引: [0, 1, 2]
s2 = pd.Series([4, 5]) # 索引: [0, 1]
df_pandas = pd.DataFrame([s1, s2])
结果:
0 1 2
0 1.0 2.0 3.0
1 4.0 5.0 NaN
关键洞察:
- 所有
Series
的索引被合并:{0, 1, 2}
→ 成为最终的列名。s2
缺少索引2
对应的值 → 在df_pandas.loc[1, 2]
处自动填入NaN
。- 这与字典构造的行为一致,再次体现了
DataFrame
的“智能对齐”和“规则矩形”设计原则。
五、混合使用字典和列表:更灵活的构造方式
Pandas 允许你混合使用字典和列表,实现更复杂的构造逻辑。
情况 1:列表中包含字典(按行构造,但每行是字典)
data = [
{'Name': 'Alice', 'Age': 25, 'City': 'Beijing'},
{'Name': 'Bob', 'Age': 30, 'City': 'Shanghai'},
{'Name': 'Charlie', 'Age': 22, 'City': 'Guangzhou'}
]
df = pd.DataFrame(data)
结果:
Name Age City
0 Alice 25 Beijing
1 Bob 30 Shanghai
2 Charlie 22 Guangzhou
- 每个字典代表一个样本(一行)
- 字典的键成为列名(特征)
- 完美符合“行是样本,列是特征”
这是从 API 或 JSON 数据创建
DataFrame
的常见方式。
情况 2:字典中包含列表(按列构造)
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 22],
'City': ['Beijing', 'Shanghai', 'Guangzhou']
}
df = pd.DataFrame(data)
结果同上。
两种方式结果一致,只是组织视角不同。
六、二维列表与 NumPy 数组:结构优先的构造方式
当使用二维列表或 NumPy 数组时,Pandas 和 NumPy 的行为完全一致:结构决定一切。
1. 二维列表构造 DataFrame
data_2d = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
df = pd.DataFrame(data_2d,
index=['Row1', 'Row2', 'Row3'],
columns=['ColA', 'ColB', 'ColC'])
结果:
ColA ColB ColC
Row1 1 2 3
Row2 4 5 6
Row3 7 8 9
- 外层列表的每个元素 → 一行
- 内层列表的每个元素 → 该行的一个值
2. NumPy 二维数组构造 DataFrame
import numpy as np
arr = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
df_numpy = pd.DataFrame(arr,
index=['Sample1', 'Sample2', 'Sample3'],
columns=['FeatureX', 'FeatureY', 'FeatureZ'])
结果:
FeatureX FeatureY FeatureZ
Sample1 1 2 3
Sample2 4 5 6
Sample3 7 8 9
与 NumPy 的一致性
# NumPy 数组
np_arr = np.array(data_2d)
print(np_arr)
# 输出:
# [[1 2 3]
# [4 5 6]
# [7 8 9]]
关键点:对于二维列表/数组:
- 外层结构 → 行
- 内层结构 → 列
- Pandas 和 NumPy 在此完全一致,都是“结构优先”而非“语义优先”
七、为什么感觉 NumPy 和 Pandas 有的时候行和列是“反过来”的?
许多人在对比以下代码时会感到困惑:
# Pandas:字典 → 列(特征)
df = pd.DataFrame({'A': s1, 'B': s2}) # s1 是列(特征)
# NumPy:列表 → 行(样本)
arr = np.array([s1.values, s2.values]) # s1.values 是行(样本)
看起来“反了”?其实不然。
- Pandas 关注语义:
{'A': s1}
表示“变量 A 的数据是 s1” → 应为列(特征) - NumPy 关注结构:
[s1.values, s2.values]
是一个列表 → 每个元素成为行(样本)
当你用
pd.DataFrame([s1, s2])
时,Pandas 和 NumPy 的行为就一致了:列表的每个元素都成为一行(一个样本)。
八、最佳实践
最佳实践:
- 想定义特征(变量)? → 用
dict
:{'feature_name': data}
- 想添加样本(观测)? → 用
list
:[sample1, sample2]
- 从 JSON/API 获取数据? → 用
[dict1, dict2]
列表 - 从 NumPy 数组转换? → 直接
pd.DataFrame(arr)
- 处理不完整或不等长数据? → 信任 Pandas 的自动对齐和
NaN
填充 - 不确定时,问自己: “这个数据是代表一个样本还是一个特征?”
九、总结
pandas.DataFrame
的构造逻辑看似复杂,实则遵循一套清晰、一致的原则:
- 核心设计: 每个样本代表一行,每个特征代表一列,这完全符合人类对表格的直觉。
- 现实映射: 这种设计与 Excel 的数据追加习惯和 MySQL 等关系型数据库的表结构一致:列(特征)是稳定的结构,行(样本)是动态的数据。
- 输入结构决定行为:
- 字典 → 特征(列)
- 列表 → 样本(行)
- 二维结构 → 外层是行,内层是列
- 语义优先于结构: Pandas 理解“变量”和“样本”的区别
Series
无固有方向: 单独的Series
只是一维数组,它的“行/列”角色由构造方式决定。- 规则矩形结构:
DataFrame
会自动对齐索引,用NaN
填充缺失值,确保数据是一个完整的二维网格。
掌握这些原则,你就能游刃有余地构造和理解 DataFrame
,不再被“按行还是按列”所困扰。
记住:Pandas 不是“复杂”,而是“智能”。它用
dict
和list
这样的“结构信号”,理解你的数据意图。
作者:aopstudio撰写初稿,Qwen进行整理和润色,aopstudio进行校对
撰写日期:2025年8月19日
标题:Pandas用Series构建DataFrame:到底是行还是列?
作者:aopstudio
地址:https://neusoftware.top/articles/2025/08/19/1755613405359.html