Series vs DataFrame:一个让你加班,一个让你准点下班

31 阅读7分钟

你有没有遇到过这种情况:

手里有一份10000行的销售数据,你想计算每个地区的平均销售额,结果用了3个for循环跑了5分钟,老板在旁边盯着进度条,尴尬得脚趾抠出了三室一厅。

或者更糟,你试图用Python原生列表处理数据,写了50行嵌套循环,最后发现数据类型不一致,程序崩溃了。

然后你百度了一圈,Stack Overflow翻了三页,最后发现——原来Pandas的DataFrame一行代码就能搞定。

别哭了,今天咱们就来彻底搞懂Pandas的两大核心:Series和DataFrame。看完这篇,下次再遇到数据分析任务,你就能优雅地说一句:"这玩意儿,我5分钟搞定。"

什么是Series?别被名字吓到了

说白了,Series就是一个带标签的一维数组

想象一下你在超市购物,有一张购物清单:

商品A: 25元
商品B: 18元
商品C: 42元

这张清单就是Series:

  • 商品A、B、C叫做索引(index)
  • 25、18、42叫做值(values)
  • 整个东西就是Series
import pandas as pd

# 创建一个Series
prices = pd.Series([25, 18, 42], index=['商品A', '商品B', '商品C'])

print(prices)
# 输出:
# 商品A    25
# 商品B    18
# 商品C    42
# dtype: int64

Series的核心价值:

  • 比普通列表多了索引,可以通过标签快速查找
  • 比字典多了数学运算能力
  • 内存效率高,处理速度飞快

那DataFrame又是什么?

如果说Series是一张购物清单,那DataFrame就是整张购物发票

import pandas as pd

# 创建DataFrame
data = {
    '商品': ['苹果', '香蕉', '橙子'],
    '价格': [25, 18, 42],
    '数量': [5, 8, 3],
    '总价': [125, 144, 126]
}

df = pd.DataFrame(data)

print(df)
# 输出:
#    商品  价格  数量   总价
# 0  苹果   25   5  125
# 1  香蕉   18   8  144
# 2  橙子   42   3  126

看到没?DataFrame就是多个Series组成的表格

  • 每一列都是一个Series
  • 每个Series都有相同的索引
  • 整个东西构成了二维数据结构

为什么要搞两个东西?这不是多此一举吗?

当初我也想:搞这么复杂干嘛?一个结构不就够了吗?

直到我踩了坑才明白:

场景1:只需要单一维度数据时,Series更合适

# ❌ 用DataFrame处理单一数据,杀鸡用牛刀
temperatures = pd.DataFrame({
    'temperature': [23, 25, 19, 30, 28]
})

# ✅ 用Series处理,简洁明了
temperatures = pd.Series([23, 25, 19, 30, 28],
                        index=['周一', '周二', '周三', '周四', '周五'])

场景2:需要复杂数据操作时,DataFrame才能救你

# 复杂数据分析
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六'],
    '年龄': [25, 30, 35, 28],
    '工资': [8000, 12000, 15000, 10000],
    '部门': ['技术', '销售', '技术', '市场']
})

# 一行代码计算每个部门的平均工资
avg_salary = df.groupby('部门')['工资'].mean()

print(avg_salary)
# 输出:
# 部门
# 市场    10000
# 技术    11500
# 销售    12000
# Name: 工资, dtype: int64

这操作用纯Python写至少要20行代码,DataFrame一行搞定!

核心操作:这些你必须会

Series常用操作

# 创建Series
scores = pd.Series([85, 92, 78, 95], index=['语文', '数学', '英语', '理综'])

# 通过索引访问
print(scores['数学'])  # 92

# 通过位置访问
print(scores[1])       # 92

# 切片
print(scores[1:3])     # 数学92, 英语78

# 数学运算
print(scores + 5)      # 每科都加5分

# 过滤
print(scores[scores > 80])  # 大于80分的科目

DataFrame常用操作

# 创建DataFrame
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五'],
    '年龄': [25, 30, 35],
    '城市': ['北京', '上海', '广州']
})

# 查看数据
print(df.head())       # 前几行
print(df.info())       # 数据信息
print(df.describe())   # 统计信息

# 选择列
print(df['姓名'])      # 返回Series
print(df[['姓名', '年龄']])  # 返回DataFrame

# 选择行
print(df.iloc[0])      # 第一行(按位置)
print(df.loc[0])       # 第一行(按索引)

# 条件过滤
print(df[df['年龄'] > 25])  # 年龄大于25的

# 添加新列
df['薪资'] = [8000, 12000, 15000]

# 删除列
df = df.drop('城市', axis=1)

实战案例:这才知道有多爽

案例1:处理销售数据

# 模拟销售数据
sales_data = {
    '日期': ['2024-01-01', '2024-01-01', '2024-01-02', '2024-01-02'],
    '产品': ['手机', '电脑', '手机', '电脑'],
    '销量': [100, 50, 120, 60],
    '单价': [3000, 5000, 3000, 5000]
}

df = pd.DataFrame(sales_data)

# 计算销售额
df['销售额'] = df['销量'] * df['单价']

# 按日期汇总
daily_sales = df.groupby('日期')['销售额'].sum()

print(daily_sales)
# 输出:
# 日期
# 2024-01-01    550000
# 2024-01-02    660000
# Name: 销售额, dtype: int64

案例2:数据清洗

# 脏数据
dirty_data = pd.DataFrame({
    '姓名': ['张三', '李四', '', '王五', None],
    '年龄': [25, '三十岁', 35, None, 28],
    '工资': [8000, 12000, -100, 15000, None]
})

# 处理缺失值
df_clean = dirty_data.dropna()  # 删除空值
df_clean = dirty_data.fillna({'年龄': 0, '工资': 0})  # 填充空值

# 数据类型转换
df_clean['年龄'] = pd.to_numeric(df_clean['年龄'], errors='coerce')

# 过滤异常值
df_clean = df_clean[df_clean['工资'] > 0]  # 工资必须大于0

踩坑提醒:别掉这些坑

坑1:索引不一致导致计算错误

# ❌ 错误示范
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([4, 5, 6], index=['a', 'c', 'd'])

result = s1 + s2
print(result)
# 输出:
# a    5.0
# b    NaN  # 没匹配上,变成NaN
# c    7.0
# d    NaN  # 没匹配上,变成NaN

# ✅ 正确做法:对齐索引或重置索引
s2 = s2.reindex(s1.index)  # 对齐索引
result = s1 + s2

坑2:链式赋值不生效

# ❌ 错误示范 - 链式赋值
df[df['年龄'] > 30]['工资'] = 20000  # 这行代码可能不会生效

# ✅ 正确做法 - 使用.loc
df.loc[df['年龄'] > 30, '工资'] = 20000

坑3:复制视图vs修改原数据

# ❌ 修改了原数据
df_subset = df[df['年龄'] > 25]
df_subset['工资'] = 99999  # 这会修改原df!

# ✅ 明确复制
df_subset = df[df['年龄'] > 25].copy()
df_subset['工资'] = 99999  # 这只修改副本

性能对比:差距有多大?

用100万行数据做个简单测试:

import pandas as pd
import numpy as np
import time

# 创建大数据集
data_size = 1000000
df = pd.DataFrame({
    'A': np.random.randn(data_size),
    'B': np.random.randn(data_size),
    'C': np.random.randn(data_size)
})

# pandas计算
start_time = time.time()
result_pandas = df['A'] + df['B'] * df['C']
pandas_time = time.time() - start_time

# 纯Python计算(仅演示,实际会很慢)
start_time = time.time()
result_python = [df['A'][i] + df['B'][i] * df['C'][i] for i in range(1000)]  # 只取1000个,否则太慢
python_time = time.time() - start_time

print(f"Pandas处理100万行用时: {pandas_time:.4f}秒")
print(f"纯Python处理1000行用时: {python_time:.4f}秒")

结果?Pandas比纯Python快了几百倍!(°□°;)

什么时候用什么?选择指南

用Series的场景:

  • 单一维度的数据(温度、价格、成绩等)
  • 需要标签索引的序列
  • 时间序列数据
  • 配置参数列表

用DataFrame的场景:

  • 多维度数据(表格数据)
  • 需要复杂的查询和过滤
  • 数据分析和统计
  • 数据清洗和转换
  • 机器学习的特征工程

记住:DataFrame是Series的超集,Series能做的DataFrame都能做,但Series更轻量。

写在最后

搞懂了Series和DataFrame,你就掌握了Pandas的80%。

但是等等,你可能还想问:那怎么读取Excel文件?怎么处理时间数据?怎么合并两个DataFrame?

这些坑,咱们下篇再填。

记住一句话:Series是一维带标签数组,DataFrame是二维表格结构。 抓住这个本质,其他都是细节。

下次再遇到数据分析任务,别再写for循环了,直接上Pandas,保准又快又稳。


延伸阅读:

你在用Pandas时遇到过什么坑?评论区聊聊,看看谁踩的坑更惨。 (*/ω*)