013、Python数据科学生态:NumPy、Pandas与SciPy实战

张开发
2026/5/19 3:24:19 15 分钟阅读
013、Python数据科学生态:NumPy、Pandas与SciPy实战
一、从一次内存溢出说起上周排查一个线上问题同事的脚本把128GB内存的服务器跑崩了。打开代码一看他用Python原生列表存了五千万条传感器数据每条数据还套了三层字典。我让他改成NumPy的float32数组内存从98GB直接降到1.2GB脚本运行时间从半小时缩短到八秒。这件事让我再次意识到数据科学不是写业务逻辑而是和计算机内存布局、CPU缓存线、向量化指令集打交道。今天我们就聊聊NumPy、Pandas、SciPy这三个让Python能硬刚C数据处理的利器。二、NumPy别再用列表做数值计算了很多人以为NumPy就是个“快点的列表”这是最大的误解。看个例子# 错误示范用列表做向量运算a[iforiinrange(1000000)]b[i*2foriinrange(1000000)]result[a[i]b[i]foriinrange(1000000)]# 这里CPU在哭# 正确姿势importnumpyasnp a_npnp.arange(1e6,dtypenp.float32)# 显式指定类型省一半内存b_npnp.arange(1e6,dtypenp.float32)*2result_npa_npb_np# 这里触发SIMD指令CPU在笑关键点在于连续内存布局。NumPy数组在内存中是连续块CPU缓存预取一次能加载一大片数据。Python列表存的是对象指针每次访问都要跳转到随机内存地址缓存命中率惨不忍睹。实战坑点# 坑1默认float64浪费内存arrnp.array([1,2,3])# 默认是np.float648字节arrnp.array([1,2,3],dtypenp.float32)# 4字节精度够用就别奢侈# 坑2视图和拷贝分不清anp.ones((1000,1000))ba[:500,:]# 这是视图没复制数据ca[:500,:].copy()# 这才是真拷贝# 改b会影响a我在这栽过跟头广播机制是NumPy的魔法但新手容易中招# 看起来能广播实际会报错Anp.random.rand(3,4,5)Bnp.random.rand(3,5)# 维度对不上# 改成 B np.random.rand(4,5) 才能广播到(3,4,5)三、PandasSQL和Excel的结合体早年我用MATLAB处理数据后来用R现在基本全用Pandas。它最厉害的是混合了关系型数据库的查询思维和电子表格的直观操作。importpandasaspd# 读取时就要考虑内存# 坏习惯直接pd.read_csv(10GB.csv)# 好习惯先探路df_samplepd.read_csv(big_data.csv,nrows1000)dtypesdf_sample.dtypes.to_dict()# 把object列转category内存立减90%forcolindf_sample.select_dtypes(include[object]):dtypes[col]categorydfpd.read_csv(big_data.csv,dtypedtypes,usecols[必要的列])# 别全读索引的坑最深dfpd.DataFrame({value:np.random.rand(1000000)},indexpd.date_range(2023-01-01,periods1000000,freqS))# 慢查询df[df.index 2023-01-02]# 快查询df.loc[2023-01-02:] # 索引已排序时loc用二分查找# 更坑的是多重索引df_multidf.set_index([col1,col2])# 查询必须从左到右df_multi.loc[(A,)]可以df_multi.loc[(slice(None), X)]效率极低分组操作是Pandas的精华但apply用不好就是性能杀手# 新手写法慢resultdf.groupby(group_col).apply(lambdax:x[value].mean()-x[value].std())# 老手写法向量化gdf.groupby(group_col)[value]resultg.mean()-g.std()# 用内置聚合避开apply四、SciPy科研计算的工具箱NumPy是基础数据结构SciPy就是上面的算法库。最近做信号处理深有体会fromscipyimportsignal,optimize,sparse# 1. 信号处理滤波器设计别自己写tnp.linspace(0,1,1000)rawnp.sin(2*np.pi*50*t)0.5*np.random.randn(1000)# 50Hz信号噪声b,asignal.butter(4,0.1,low)# 4阶低通截止频率0.1*Nyquistfilteredsignal.filtfilt(b,a,raw)# 零相位滤波比lfilter好用# 2. 稀疏矩阵别用np.zeros存稀疏数据# 10000x10000矩阵只有1%非零用np.zeros占800MBsparse_matrixsparse.csr_matrix((10000,10000))# 实际项目里我从800MB降到8MB解线性方程快了几百倍# 3. 优化问题调参黑魔法defloss(params):a,b,cparamsreturnnp.sum((y_true-a*np.exp(-b*x)c)**2)resultoptimize.minimize(loss,[1,0.1,0],methodL-BFGS-B,# 边界约束选这个bounds[(0,None),(0,1),(-10,10)])# 初值很重要我常多试几组随机初值五、三剑客配合实战真实项目里这三个库是混用的# 场景传感器数据异常检测importnumpyasnpimportpandasaspdfromscipyimportstats# 1. Pandas读数据、清洗dfpd.read_csv(sensor.csv,parse_dates[timestamp])df[value]pd.to_numeric(df[value],errorscoerce)# 非数字转NaNdfdf.dropna().set_index(timestamp)# 2. NumPy做滑动窗口统计valsdf[value].values# 转NumPy数组后续操作快10倍window_size60# 向量化滑动窗口均值比for循环快window_meannp.convolve(vals,np.ones(window_size)/window_size,modevalid)# 3. SciPy做异常检测z_scoresnp.abs(stats.zscore(vals[-len(window_mean):]))anomaliesz_scores3# 3sigma原则# 4. 结果回Pandas做可视化准备df_resultdf.iloc[window_size-1:].copy()# 对齐窗口df_result[window_mean]window_mean df_result[is_anomaly]anomalies六、一些血泪经验内存管理监控df.memory_usage(deepTrue)大文件用chunksize分块读。曾经有个项目因为没设dtype读CSF时内存爆了被运维打电话骂。索引策略时间序列数据一定要用时间戳索引df.asfreq()检查缺失点。某次分析工厂设备数据因为采样间隔不均匀算出的FFT全是噪声。向量化优先看到for循环先想想能不能用np.vectorize虽然它也是伪向量化或Pandas内置方法。我重构过一个循环用np.einsum把三天运行缩到三分钟。数据类型敏感float32和float64在累加时误差能差出几个数量级。金融计算别用float32科学计算很多时候够用。不要迷信默认值pd.merge()不指定suffixes可能覆盖列np.sum()默认axisNone会全数组求和。每个函数都看眼文档省下半天调试时间。最后说两句数据科学库用得好不好关键看是否理解底层。NumPy数组本质是C数组Pandas是带索引的NumPy集合SciPy是Fortran/C的Python封装。遇到性能瓶颈时别急着换Cython先看看是不是数据布局出了问题。我见过有人把列存数据强行按行遍历优化到C也救不了。工具越高级越容易掩盖问题。偶尔用%timeit测测基础操作用memory_profiler看看内存变化保持对数据的直觉——这才是资深工程师和调包侠的区别。

更多文章