1. 理解数据塑形的核心需求
数据科学项目中80%的时间都花在数据预处理上,这句话你可能听过无数次。但真正开始处理一个具体项目时,才会明白为什么数据塑形如此重要。想象你正在准备一顿晚餐,食材买回来了,但有的需要切块,有的需要切片,还有的需要腌制——数据预处理就是这样的过程。
上周我处理过一个电商用户行为数据集,原始数据就像一堆杂乱无章的食材。模型要求输入必须是特定维度的张量,而原始数据却是二维表格;分类特征还是"A/B/C"这样的字符串;数值特征的尺度差异大到离谱。这时候就需要np.newaxis来调整维度,pd.get_dummies来处理分类变量,配合fit/norm/hist来验证数据分布。
为什么这些操作如此关键?以最简单的神经网络为例,输入层的神经元数量是固定的,如果你的数据维度不匹配,就像试图把方形积木塞进圆形孔洞。我在第一次尝试时忽略了维度匹配,结果模型直接报错"维度不匹配",调试了两小时才发现问题。
2. np.newaxis的维度魔法
2.1 基础用法与原理
第一次看到np.newaxis时,我觉得这命名真是直白得可爱——"新轴"。它的作用就是在指定位置插入一个新维度。来看这个实际案例:
import numpy as np price_data = np.random.rand(100) # 100个产品价格 print(price_data.shape) # 输出 (100,)假设我们要用这个数据训练LSTM模型,但LSTM要求输入形状是(样本数, 时间步, 特征数)。我们的数据只有(100,),怎么办?
# 错误示范:直接reshape会丢失维度信息 reshaped = price_data.reshape(100, 1) print(reshaped.shape) # (100, 1) # 正确做法:使用np.newaxis expanded = price_data[:, np.newaxis] # 等同于price_data.reshape(100, 1) print(expanded.shape) # (100, 1)虽然这个简单例子中reshape也能达到同样效果,但在复杂操作链中,np.newaxis的可读性更好。我在处理3D医学影像数据时,经常需要这样扩展维度:
mri_scan = np.random.rand(256, 256) # 单张MRI切片 volumetric_data = mri_scan[np.newaxis, ..., np.newaxis] print(volumetric_data.shape) # (1, 256, 256, 1) 适合输入3D CNN2.2 高级应用场景
在图像处理中,经常需要批量处理图片。假设我们有100张RGB图片(每张256x256),常规存储是(100,256,256,3)。但某些框架要求通道在前:(100,3,256,256)。这时候np.newaxis就派上用场了:
images = np.random.rand(100, 256, 256, 3) # 错误的转置方式会打乱数据 # 正确做法: channels_first = images.transpose(0, 3, 1, 2)更复杂的例子是处理视频数据。上周我处理过一个体育动作识别项目,原始数据是(视频数,帧数,高,宽,通道)。但模型需要(批次,帧数,通道,高,宽)。通过组合np.newaxis和transpose,我们实现了完美转换:
video_data = np.random.rand(10, 30, 1080, 1920, 3) # 10个视频,每个30帧 processed = video_data[:, :, np.newaxis, ...].transpose(0,1,4,2,3) print(processed.shape) # (10, 30, 3, 1080, 1920)3. 数据排序与分布分析
3.1 使用np.argsort进行智能排序
argsort是我最喜欢用的NumPy函数之一。它不直接排序数据,而是返回排序后的索引。这在处理关联数据时特别有用。比如电商场景中,我们既要按价格排序,又要保持价格与产品ID的对应关系:
product_ids = np.array([1001, 1002, 1003, 1004]) prices = np.array([299, 599, 199, 899]) sort_indices = np.argsort(prices) print("排序索引:", sort_indices) # [2 0 1 3] print("按价格排序的产品ID:", product_ids[sort_indices]) # [1003 1001 1002 1004]在推荐系统中,我经常用argsort来获取Top-N推荐。比如用户-物品评分矩阵中,对每个用户找出评分最高的3个物品:
user_ratings = np.random.rand(1000, 100) # 1000用户对100物品的评分 top3_indices = np.argsort(user_ratings, axis=1)[:, -3:]3.2 可视化验证数据分布
数据质量决定模型上限。我习惯在预处理前后都检查数据分布。上周分析金融交易数据时,发现原始金额呈现严重长尾分布:
import seaborn as sns import matplotlib.pyplot as plt from scipy.stats import norm transactions = np.random.exponential(scale=1000, size=1000) sns.distplot(transactions, fit=norm) plt.title("原始交易金额分布") plt.show()经过对数变换后,数据更接近正态分布:
log_trans = np.log1p(transactions) sns.distplot(log_trans, fit=norm) plt.title("对数变换后分布") plt.show()对于高维数据,我常用pairplot快速检查特征间关系:
df = pd.DataFrame(np.random.randn(1000, 4), columns=['age', 'income', 'spending', 'savings']) sns.pairplot(df) plt.show()4. 分类特征编码实战
4.1 get_dummies基础应用
分类变量是结构化数据中的常客。上周处理用户数据集时,遇到"职业"字段有12种取值。直接喂给模型?且慢!字符串输入会让大多数模型直接报错。
users = pd.DataFrame({ 'age': [25, 32, 45], 'occupation': ['teacher', 'engineer', 'teacher'] }) dummies = pd.get_dummies(users, columns=['occupation']) print(dummies)输出结果:
age occupation_engineer occupation_teacher 0 25 0 1 1 32 1 0 2 45 0 14.2 处理高基数分类变量
当分类变量取值很多时(如城市名),直接get_dummies会导致维度爆炸。这时我通常:
- 将低频类别合并为"其他"
- 使用均值编码或目标编码
- 考虑嵌入层(深度学习场景)
cities = ['北京']*300 + ['上海']*250 + ['广州']*200 + ['深圳']*150 + ['杭州']*100 + ['其他']*50 df = pd.DataFrame({'city': np.random.choice(cities, 1000)}) # 方法1:只保留高频城市 top_cities = df['city'].value_counts().nlargest(5).index df['city'] = df['city'].where(df['city'].isin(top_cities), '其他') # 方法2:均值编码(假设有目标变量y) df['y'] = np.random.randint(0, 2, size=1000) mean_encoding = df.groupby('city')['y'].mean() df['city_encoded'] = df['city'].map(mean_encoding)4.3 处理未见过的类别
生产环境中常遇到训练时没见过的类别。好的预处理应该能处理这种情况:
# 训练数据 train = pd.DataFrame({'color': ['red', 'blue', 'green']}) # 测试数据可能出现新颜色 test = pd.DataFrame({'color': ['red', 'yellow']}) # 安全做法:先获取所有可能取值 all_colors = train['color'].unique() dummies_train = pd.get_dummies(train.assign( color=lambda x: x['color'].where(x['color'].isin(all_colors), 'other') )) dummies_test = pd.get_dummies(test.assign( color=lambda x: x['color'].where(x['color'].isin(all_colors), 'other') ))5. 完整数据处理流水线示例
让我们把这些技术串联起来,处理一个真实场景的数据集。假设我们有一组房屋数据:
import pandas as pd import numpy as np # 模拟数据 np.random.seed(42) data = { 'area': np.random.normal(100, 30, 1000), 'rooms': np.random.randint(1, 6, 1000), 'district': np.random.choice(['A', 'B', 'C', 'D'], 1000), 'price': np.random.normal(500000, 150000, 1000) } df = pd.DataFrame(data) # 1. 调整数值特征维度 X_num = df[['area', 'rooms']].values X_num = X_num[:, :, np.newaxis] # 从(1000,2)变为(1000,2,1) # 2. 处理分类特征 X_cat = pd.get_dummies(df['district'], prefix='dist') # 3. 标准化数值特征 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_num_scaled = scaler.fit_transform(X_num.squeeze())[..., np.newaxis] # 4. 合并特征 X_processed = np.concatenate([ X_num_scaled, X_cat.values[:, :, np.newaxis].squeeze() ], axis=1) # 5. 验证分布 plt.figure(figsize=(12, 6)) plt.subplot(121) sns.distplot(df['price'], fit=norm) plt.title('原始价格分布') plt.subplot(122) sns.distplot(np.log1p(df['price']), fit=norm) plt.title('对数变换后分布') plt.show()这个流程展示了如何将各种数据塑形技术组合使用。在实际项目中,我通常会把这个流程封装成可复用的预处理类,特别是当需要处理多个相似数据集时。