Pandas 是构建在 NumPy 之上的数据分析库,两者紧密集成。NumPy 提供了高效的数组运算,Pandas 则在此基础上增加了标签、索引、数据对齐和缺失值处理等功能。掌握它们之间的交互,可以让你在数据分析中同时利用两者的优势。
Pandas 的 Series 是一维带标签的数组,底层数据存储在 NumPy 数组中。可以轻松地在 Series 和 ndarray 之间转换。
通过 .values 或 .to_numpy() 获取底层的 NumPy 数组。推荐使用 .to_numpy(),因为它更明确且支持 future 行为。
import numpy as np
import pandas as pd
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
print("Series:\n", s)
# 转换为 ndarray
arr = s.to_numpy() # 或 s.values
print("ndarray:", arr)
print("类型:", type(arr))
输出:
Series:
a 10
b 20
c 30
dtype: int64
ndarray: [10 20 30]
类型:
使用 pd.Series() 构造函数,可以指定 index。
arr = np.array([100, 200, 300])
s_new = pd.Series(arr, index=['x', 'y', 'z'])
print("从 ndarray 创建的 Series:\n", s_new)
输出:
从 ndarray 创建的 Series:
x 100
y 200
z 300
dtype: int64
DataFrame 是二维表格,每一列可以是不同的数据类型,但整体数据可以转换为二维 NumPy 数组(所有列类型相同)或结构化数组。
使用 .to_numpy() 或 .values 得到二维数组。如果各列数据类型不同,会向上转型为兼容类型(通常为 object)。
df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4.0, 5.0, 6.0],
'C': ['x', 'y', 'z']
})
print("DataFrame:\n", df)
arr = df.to_numpy()
print("转换为 ndarray:\n", arr)
print("数据类型:", arr.dtype) # 可能是 object,因为包含字符串
输出:
DataFrame:
A B C
0 1 4.0 x
1 2 5.0 y
2 3 6.0 z
转换为 ndarray:
[[1 4.0 'x']
[2 5.0 'y']
[3 6.0 'z']]
数据类型: object
通过 pd.DataFrame() 传入二维数组,并可指定列名和索引。
arr = np.random.rand(3, 2)
df_new = pd.DataFrame(arr, columns=['col1', 'col2'], index=['r1', 'r2', 'r3'])
print("从 ndarray 创建的 DataFrame:\n", df_new)
NumPy 的结构化数组(复合 dtype)可以完美转换为 DataFrame,字段名成为列名。
dtype = [('name', 'U10'), ('age', 'i4'), ('weight', 'f8')]
structured_arr = np.array([('Alice', 25, 55.5), ('Bob', 30, 75.2)], dtype=dtype)
df_from_struct = pd.DataFrame(structured_arr)
print("结构化数组转 DataFrame:\n", df_from_struct)
Pandas 的 Series 和 DataFrame 兼容 NumPy 的通用函数(ufunc),可以直接应用,结果保留索引和列名。
df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6]
})
# 应用 np.log
log_df = np.log(df)
print("np.log(df):\n", log_df)
# 应用 np.sqrt
sqrt_df = np.sqrt(df)
print("np.sqrt(df):\n", sqrt_df)
两个 DataFrame 之间或 DataFrame 与 Series 之间的运算也会利用广播机制。
# DataFrame 与 Series 相加(沿行广播)
s = pd.Series([10, 20], index=['A', 'B'])
result = df + s
print("df + s:\n", result)
NumPy 的聚合函数可以直接应用于 DataFrame,返回聚合后的 Series(保留列名)或标量(对整个 DataFrame)。
print("np.sum(df):\n", np.sum(df)) # 各列之和
print("np.mean(df, axis=1):\n", np.mean(df, axis=1)) # 各行均值
虽然 Pandas 本身已很高效,但某些操作(如逐行 apply)可能较慢。通过提取底层 NumPy 数组并向量化,可以显著提升性能。
df = pd.DataFrame({
'x': np.random.rand(1000000),
'y': np.random.rand(1000000)
})
# 慢速:使用 apply 逐行计算
def my_func(row):
return np.sqrt(row['x']**2 + row['y']**2)
%timeit -n1 -r1 df.apply(my_func, axis=1)
# 快速:使用 NumPy 向量化
%timeit -n1 -r1 np.sqrt(df['x'].to_numpy()**2 + df['y'].to_numpy()**2)
向量化版本通常快几十倍。
Pandas 的布尔索引基于 NumPy,但直接使用 NumPy 条件数组有时更快。
mask = (df['x'] > 0.5) & (df['y'] < 0.5) # Pandas 布尔 Series
filtered = df[mask]
# 使用 NumPy 数组作为掩码(也兼容)
mask_np = (df['x'].to_numpy() > 0.5) & (df['y'].to_numpy() < 0.5)
filtered_np = df.iloc[mask_np] # 需使用 iloc
NumPy 的线性代数模块(np.linalg)和随机数生成可以直接在 Pandas 数据上使用,但通常需要先提取数组。
# 协方差矩阵
cov_matrix = np.cov(df.T) # df.T 转置使每行是一个变量
# 矩阵乘法
result = df.values @ df.values.T # 二维数组乘法
# 随机数填充新列
df['rand'] = np.random.default_rng().normal(size=len(df))
NaN 表示缺失值,NumPy 函数如 np.mean 会返回 NaN,但 np.nanmean 可以处理。Pandas 的 .mean() 默认跳过 NaN。df1 = pd.DataFrame(np.random.rand(1000, 100))
df2 = pd.DataFrame(np.random.rand(1000, 100))
# 有对齐
%timeit -n10 df1 + df2
# 无对齐(直接操作数组)
%timeit -n10 pd.DataFrame(df1.values + df2.values, index=df1.index, columns=df1.columns)
# 创建带有缺失值的 DataFrame
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, 7, 8],
'C': [9, 10, 11, 12]
})
# 使用 NumPy 的 nan 函数填充均值
col_means = np.nanmean(df.values, axis=0) # 各列均值
df_filled = df.fillna(col_means)
# 使用 NumPy 计算标准化
df_normalized = (df_filled - np.mean(df_filled, axis=0)) / np.std(df_filled, axis=0)
print("标准化后的 DataFrame:\n", df_normalized)
%timeit 测试性能,找到最适合你的数据大小和操作的模式。通常,对于大规模数值计算,提取 NumPy 数组进行处理,然后重新构建 Pandas 对象是最佳实践。
NumPy 和 Pandas 是 Python 数据科学生态系统中的黄金搭档。通过灵活地在两者之间转换和利用各自的优势,可以编写出既高效又易读的数据处理代码。掌握交互技巧,能让你的数据分析工作如虎添翼。下一章我们将讨论 NumPy 的最佳实践和常见陷阱。