资讯详情 Pandas数据清洗与格式转换:科研绘图前的必修课
📅 2026/10/15 4:56:48
写在前头做科研绘图的人经常有个错觉以为学会了画图工具就等于会出图了。真正跑过几条数据就知道从原始实验记录到一张能被期刊接收的图中间最脏最累的活全在数据处理上。Pandas 就是干这个的而“数据清洗与格式转换”这门基本功直接决定了你画出来的图是能讲清楚故事还是一堆漂亮的垃圾。这篇文章就把我在实际科研项目里常用的 Pandas 数据清洗和格式转换流程拆开揉碎讲一遍挂了“科研绘图基础”的名头但里面每一步都是为了让后续 matplotlib/seaborn 画图时不再手忙脚乱。内容适合那种已经跑通 Python、但每次处理数据都靠百度拼代码的科研新手也适合想把自己的数据处理流程整理得更规范的老手。这里面没有花活全是能直接抄走用的套路。1. 为什么绘图前非要折腾数据清洗与格式转换科研数据从采集到出图路径大概是这样的仪器导出的原始文件、手工记录的表单、爬下来的公开数据这些东西极少能直接喂给绘图函数。最常见的情况是列名混乱、单位挤在字符串里、日期被读成字符串、缺失值一堆、同一份数据长宽格式不对任何一条都足以让绘图报错或者更气人的是图能画出来但坐标刻度、图例完全是乱的。1.1 绘图失败的真正原因往往不是绘图代码很多初学者画不出来图第一反应是去查绘图函数怎么用查了半天发现绘图代码没问题回头才发现是数据结构不对。比如 seaborn 的pointplot要求长格式数据而你手里的数据是宽格式直接画就成了几条串不起来的折线再比如 matplotlib 的plot要求 x 轴数据是数值型结果你的列因为混入个别文本Pandas 读进来就成了 object 类型画出来的横轴全部被当成字符串等差刻度全废。这些坑本质都不是绘图的坑而是数据准备没做到位。这里可以套一个比较形象的比喻数据处理是做饭前的洗菜切菜。菜不洗、不切再好的锅也炒不出一盘像样的菜。Pandas 就是那把厨刀数据清洗就是摘掉烂叶、去根、切段格式转换则是决定这盘菜是切丁还是切丝——后面的绘图库定了前面就得按它的胃口准备食材。1.2 清洗与转换要做什么心里先有个清单结合我做过的几个模拟项目数据比如测量仪器的输出、问卷打分表、传感器时序记录我给自己固定了一套处理顺序其实也是大多数人公认的标准流程先看结构读进来马上head()、info()、describe()三件套搞清楚长啥样、类型对不对、缺不缺。再清脏数据缺失值、重复行、异常值一条条处理。统一类型数值列、时间列、分类列全部转成正确的 dtype。调整形状宽表转长表、长表转宽表、哑变量化匹配后面的绘图需求。存盘下线处理完导出成干净的新文件再进下一步画图。这套流程的先后顺序其实是有讲究的。假如不先看结构就闷头处理类型可能连哪些列要处理都不知道不清缺失值就做长宽转换转换之后缺失组合会成倍膨胀后面再处理更麻烦。所以宁可每步慢一点也要按顺序走省得返工。2. 环境准备与数据加载一切从读进来开始Pandas 本身不需要什么重型环境一个 Python 的虚拟环境加pip install pandas就够用。科研绘图的话顺手装上 matplotlib、seaborn后面省事。我建议用 Jupyter Notebook 或者 Jupyter Lab 来做这部分工作因为数据处理是高度交互式的每跑一步就要看一眼中间结果Notebook 这种细胞式的操作比写一个长脚本然后一次跑到底要顺手得多。2.1 加载常见的科研表格数据最常见的科研数据格式就是 CSV 和 ExcelPandas 都有现成接口。先看 CSVimport pandas as pd df pd.read_csv(experiment_data.csv) print(df.head()) print(df.info())read_csv背后做了不少自动推断的活比如它会自动把看起来像整数的列识别成 int64把带小数的识别成 float64但“看起来像数字其实混入了文本”的情况它就没辙了只能读成 object 类型。这里分享一个不少教程不会刻意强调的细节read_csv里有个参数dtype可以在读入时就指定每列类型对那种明确知道类型的文件直接写死dtype{温度: float32}既能避免自动推断出错也能省内存。Excel 文件对应pd.read_excel注意它需要安装openpyxlread .xlsx或者xlrdread .xls如果报 ModuleNotFoundError先补这个依赖。另外 Excel 的多 Sheet 表要加sheet_name参数可以读指定表也可以传入列表一次读多个表再自己 merge。2.2 预览数据的三个标准动作数据读进来不要急着处理先做三件事我称之为“三件套”# 1. 看前面几行直观感受数据的列名、单位、大致范围 print(df.head(10)) # 2. 看列名、非空数量、dtype这里能一次性暴露出类型问题和缺失情况 print(df.info()) # 3. 看数值型列的统计量发现最小值、最大值明显离谱的列就要留意 print(df.describe())info()输出里如果看到某个本来就该是数值的列显示object那就是类型有问题牵涉到后面格式转换如果看到非空计数比总行数少那就是有缺失。describe()会忽略文本列只统计数值列但如果某列被误读成 object统计里就会漏掉它这也算一种变相的“报警”。这三个动作做下来基本能判断出这份数据要花多大力气清洗。2.3 关于列名的初始处理国内不少科研数据文件直接拿中文做表头Pandas 能读但部分绘图库对中文标签支持不算友好默认字体缺字形导出的图全是方块。所以我在数据加载之后做的第一件事往往是统一改列名去空格、去特殊符号、改成英文或拼音。这一步看着不起眼但能省掉后面几十次报错。# 把列名里的空格换成下划线并转为小写 df.columns [col.strip().replace( , _).lower() for col in df.columns] # 或者用 rename 来个别改名 df df.rename(columns{温度(℃): temp, 压力(kPa): pressure})这个阶段的目标不是一次到位而是让列名变得“可以放心在代码里敲”没有特殊字符、没有点号、没有括号后面不管用df[temp]还是df.temp都顺手。3. 数据清洗实操缺失、重复、异常一网打尽这一部分是整个流程里最费精力、也最体现经验的环节。很多人觉得清洗就是调两个函数实际上每一步都得结合你对数据的理解来做决定。比如缺失值到底是该删还是该填没有标准答案完全取决于业务背景。3.1 缺失值处理先定位再决定策略定位缺失值最直接的方式是isna()配合sum()一眼看每列缺多少个print(df.isna().sum())如果缺失占比很小比如几千行里缺个位数直接删除这些行最省心df_clean df.dropna(subset[温度, 时间])dropna默认是删整行但在科研数据里“整行全空”和“某几列为空”是两个概念。subset参数用来指定只看某些关键列只要关键列不为空其他列空着也可以保留。如果你处理的是时间序列删除行可能会破坏连续性这时候fillna按需填充就更合适# 用前后数值插值填充 df[温度] df[温度].interpolate() # 用整列均值填充仅在波动小、缺失随机的场景下用 df[温度] df[温度].fillna(df[温度].mean())我踩过的坑是盲目用全局均值填充会给后续统计挖坑。如果某一列本来就有明显的分段特征比如不同工况下的温度全局均值会把每个分段都拉向一个中间值失真很严重。这种情况下要么分组填均值要么用插值。3.2 重复数据看似没扣分点实则影响统计和作图重复数据在科研原始表里也常出现尤其是手工录入或者多批次合并之后。重复行如果不清理累计曲线会多算权重散点图上则会出现大量重叠点误导密度判断。先看重复情况print(df.duplicated().sum()) df_clean df.drop_duplicates()如果重复的标准不是“整行完全相同”而是“某些关键列相同就认为是重复”要加subset参数df_clean df.drop_duplicates(subset[实验编号, 采集时间], keepfirst)keepfirst保留第一次出现的行keeplast保留最后一次。至于怎么选我习惯保留最后一次因为很多仪器反复测试时最后一次往往是调整好之后的正式结果但这里没有统一答案还是要看你的数据来源。3.3 异常值用条件筛选把它们揪出来异常值分两种一种是真错误比如温度传感器短接导致跳变记录成 9999这种必须处理另一种是真实物理过程比如偶发的极端事件这种不能随便删。我的做法是先标记再看上下文而不是直接删# 找出温度列里超出合理范围的行 mask (df[temp] -20) | (df[temp] 80) outliers df[mask] print(outliers)确认是异常值后根据要做的分析决定处理方式。如果是明显记录错误可以直接剔除df_clean df[~mask]如果是散点图时不想让个别点带崩坐标轴范围但又不确定它是否真实我不建议在数据清洗阶段粗暴删除更好的做法是画图时单独设置坐标轴范围让异常点落在视野外保留数据完整性。等后续分析需要时再单独决定。“异常值不一定要删”这个观点很多人用两年数据后才体会得到。3.4 数据类型转换把“长得像数字”变成“真的是数字”科研数据最常见的一个问题就是数值列被读成 object。典型场景Excel 里有几行记录写成“12.3℃”或者数字和空格混在一起read_csv就会把整列推断成字符串。处理路径是先清洗字符串里的杂质再转数值类型。df[浓度] df[浓度].astype(str).str.replace(mg/L, ).str.strip() df[浓度] pd.to_numeric(df[浓度], errorscoerce)这里errorscoerce的意思是转换失败时置为 NaN而不是直接抛异常终止整个流程。这是我在实际操作里非常依赖的一个参数。转完之后再用isna()去查看哪些值没转成功往往能溯源到原始数据里的脏字符。日期时间列也有类似套路。很多仪器导出的时间格式里带着时区、单位或者中文“月”字标准to_datetime可能解析不了可以通过format参数显式指定df[采集时间] pd.to_datetime(df[采集时间], format%Y-%m-%d %H:%M:%S)如果你的时间格式不统一先不加format让 Pandas 自动猜测它会尽力解析大部分常规格式但解析结果如果出现大量 NaT就要回头检查原始字符串格式了。时间的统一转换是后面做时序图、趋势分析的根基早晚要处理。3.5 字符串列里的隐形脏值空格、大小写、换行符科研数据不只是数字还有实验条件、样品名、操作者等文本列。这些列的脏问题往往都很“隐形”比如字符串带着前后空格肉眼看不出来分组统计时就会被拆成两个组“Batch1”和“batch1”大小写不一致同理也会被当成两个组。处理起来很机械但必须做df[样品名] df[样品名].str.strip().str.lower()如果字符串里混着换行符、制表符可以用.replace替换掉df[备注] df[备注].str.replace(\\n, , regexTrue)这些操作单独看都不起眼但组合在一起能避免后面画分组图时图例上多出好几条莫名其妙的“幽灵分组”。4. 格式转换让数据形状匹配绘图库的口味清洗完之后数据内容基本干净了但形状未必适合绘图。seaborn 这类库最擅长处理的是长格式数据tidy data也就是每一行是一个观测、每一列是一个变量。而实际记录时我们经常遇到宽格式数据每一行是一个样品、每一列是一个测量条件或时间点。两者之间需要来回转换这就是格式转换的核心任务。4.1 宽表转长表melt 是绘图神器举个模拟例子一张表列名是三个时间点 t1、t2、t3每行是一个样品单元格是对应时间点的测量值。这种宽表直接画图得写好几条 plot 命令转成长表后seaborn 一行就能画分组折线。df_long df.melt( id_vars[样品名], # 保留不变的列 value_vars[t1, t2, t3], # 要拉长的列 var_name时间点, value_name测量值 ) print(df_long.head())转完之后原来三列的数据变成两列“时间点”标识是哪一列“测量值”记录数值。seaborn.lineplot(datadf_long, x时间点, y测量值, hue样品名)就能画出多组曲线。注意melt对应的逆操作叫pivot或pivot_table后面细讲。4.2 长表转宽表pivot 和 pivot_table 的分工有时候做热力图或相关性矩阵需要把长表转回宽表。最经典的是把“行是变量、列是分组”的统计结果还原成矩阵。pivot适合没有重复索引的简单场景一旦同样的行列组合出现多条记录pivot会直接报错“Index contains duplicate entries”。这时候就得换成pivot_table它会自动做聚合# pivot 简单版假定无重复 df_wide df_long.pivot(index样品名, columns时间点, values测量值) # 有重复时的兜底版对重复取均值 df_wide2 df_long.pivot_table( index样品名, columns时间点, values测量值, aggfuncmean )我在实际项目里第一次用pivot就撞上了重复索引报错后来养成了习惯凡是从长表转宽表一律默认用pivot_table除非我明确知道数据里没有重复。aggfunc除了mean还可以换sum、count、first等看你要什么语义。4.3 groupby 汇总加 reset_index另一种“伪宽表”科研论文里常需要按组计算均值、标准差再做误差棒图。groupby可以一步出结果stats ( df_long.groupby([时间点, 样品名])[测量值] .agg([mean, std, count]) .reset_index() ) print(stats)reset_index()这一步很容易漏。如果不加结果里分组列会被塞进索引后续按列名取数会很不顺手。加上之后分组列回到普通列stats就是一张干净的、可以直接画误差棒的统计表。误差棒画法import seaborn as sns sns.pointplot(datastats, x时间点, ymean, hue样品名)如果还想把误差棒画上去可以用 matplotlib 的 errorbar但从点线图角度讲seaborn 的 pointplot 就能满足大部分需求。4.4 分类变量转哑变量get_dummies 与 one-hot很多绘图场景不需要哑变量但做统计建模或某些需要数值型输入的库比如聚类、回归就必须要。Pandas 里一行生成哑变量dummies pd.get_dummies(df[处理组], prefix组别) df pd.concat([df, dummies], axis1)生成后的每列取值 0 或 1表示是否属于该类别。这里有个细节如果后面要做线性模型通常要丢掉一列避免共线性但只做数据清洗和画图演示不强制丢列。get_dummies对中文列名也友好会自动在列名前面加前缀。如果你不想让原来那列继续保留用pd.concat(drop_firstTrue)可以直接去掉第一个类别列。4.5 时间序列的重采样与周期转换时间相关的科研数据常需要把秒级数据聚合成分钟级、把日数据聚合成周数据或者反过来补齐缺失时间点。这属于时间维度的“格式转换”。关键一步是先确保时间列是 datetime 类型然后设为索引df[采集时间] pd.to_datetime(df[采集时间]) df df.set_index(采集时间) # 每分钟求均值 df_min df[测量值].resample(1min).mean()resample支持5min、H小时、D天、W周等。如果原始时间索引有缺口可以用resample(...).asfreq()生成规则序列缺失位置填 NaN再决定是否插值。这个操作在做长时间序列趋势图时几乎是标配。5. 绘图前的收尾整理索引导航、排序与保存数据清洗和形状转换都做完了但到真正画图前还有几个“桌面整理”级别的动作做不做直接影响画图的效率和数据输出的可靠性。这一步常常被教程略过但踩过坑的人都知道有多重要。5.1 索引管理与重命名set_index、reset_index这两个函数在实操里反复出现。很多人在做完 groupby 或 melt 之后索引变得乱七八糟可能是多重索引、可能是默认 0..n 的行号、也可能是分组名。绘图时如果要用“行号”来对齐某些列就统一先reset_index(dropTrue)一把把索引理成干净的数字序列df_final df_clean.reset_index(dropTrue)需要以某列作为图表横轴时很多人喜欢set_index但其实画图时直接在 x 参数里指定列名也一样。这里涉及一个“隐性索引陷阱”如果数据里存在重复索引某些绘图库做数据对齐时会悄悄按索引匹配导致结果错乱。所以收尾阶段统一规范化索引能在源头规避这类问题。5.2 排序让折线图不来回乱跳散点图、折线图非常吃 x 轴顺序。如果 x 轴列是数值且没有按大小排好matplotlib 画出来的折线就会在 x 方向上乱跳。处理很简单df_final df_final.sort_values(by浓度).reset_index(dropTrue)如果需要按多列排序sort_values(by[样品名, 浓度])即可。排序对箱线图、小提琴图也有意义因为分组绘图的顺序往往就是类别首次出现的顺序先对类别列排序可以控制图中组的排列。虽然 seaborn 有order参数能直接指定组顺序但提前在数据层面排好也是一种更稳的做法。5.3 保存处理结果给下一步画图留好干净底稿处理好的数据一定要单独存一份不要覆盖原始数据。我习惯加后缀_clean.csv或放到processed/目录这样原始数据还在后续分析流程可以追溯。保存代码df_final.to_csv(processed/experiment_clean.csv, indexFalse, encodingutf-8-sig)indexFalse是为了不把行索引写进文件encodingutf-8-sig是专门针对 Windows 下 Excel 打开 CSV 乱码的民间偏方其中 BOM 头能让 Excel 正确识别 UTF-8。如果你后续要给同事用 Excel 看图存 Excel 也可以df_final.to_excel(processed/experiment_clean.xlsx, indexFalse)保存出来的干净文件实际上就是整个数据处理流程的“输出交付物”。它一方面能直接拿给绘图脚本用另一方面也方便换一种绘图库时不需要重新处理一遍数据。6. 常见问题与排查技巧实录数据处理做多了你会发现报错和异常来来去去就那么几类。我这里把在模拟项目和日常教学中撞过的问题整理成速查按出现频率排个序每一条都是实打实的坑和对应的解法。6.1 中文乱码与编码问题读取 CSV 时出现乱码最常见的两个原因文件本身是 GBK 编码但read_csv默认按 UTF-8 解析或者反过来。解决办法就是显式指定编码df pd.read_csv(data.csv, encodinggbk) # 如果还乱可试 gb18030、utf-8、latin1从经验看先问交付文件的人用了什么编码最省事。我见过同一批次文件里一半是 UTF-8 一半是 GBK 的情况这时只能分块读取或者在转换工具里批量统一编码。保存输出时上面提到用utf-8-sig这是给 Excel 用户留的余地。6.2 列名带点号、空格或引号导致的各种诡异报错用df.colname访问列时只要列名里有空格、点号、中划线就会直接报 AttributeError。甚至会因为列名里隐藏着不可见字符而找不到列。处理办法很简单从一开始就把列名规范化见 2.3 节。如果已经背上一篇代码临时可以用df[列名]来访问方括号访问比点号访问对奇怪列名宽容得多。6.3 astype 转换报错或静默失败df[列].astype(int)遇到有缺失值或者空字符串时可能会抛异常或者转换出不可预期的结果。更稳的路径是用pd.to_numeric(..., errorscoerce)先转出 NaN再统一处理 NaN。这条我已经反复使用几乎成了条件反射。注意转完之后的列可能从 int 变成 float因为 NaN 需要浮点数表示这属于 Pandas 的正常做法不是 bug。6.4 长宽转换时遇到 “Index contains duplicate entries”见到这个报错不要慌它表示你pivot时指定的 index-columns 组合有重复记录。解决方案有两个一是先drop_duplicates()按业务规则去重二是换成pivot_table并用aggfunc聚合。从我经验看后者更通用因为保留了数据里的信息密度不轻易删数据。6.5 大文件读取慢与内存占用过高几 GB 的单表在普通笔记本上容易把内存吃满。三个实用手段读文件时指定dtype避免 Pandas 自动推断带来的额外内存开销用read_csv(..., chunksize10000)分批读取边读边处理只读需要的列usecols[样品名, 测量值]。这些不一定要每次都用但一旦遇到大数据量提前知道手段能省下大量反复试错的时间。6.6 排序后绘图顺序依然不对有时候明明用sort_values按数值列排了序画出来的折线图还是乱序。这通常是因为绘图库默认按索引顺序连接点而不是按你的排序。检查一下是否在排序后又加了reset_index以及是否存在多重索引残留必要时可以在绘图命令里直接指定 x 轴的顺序参数比如 seaborn 的order[...]或者重采样成规则序列再画。7. 几个让你少走弯路的实操心得数据清洗和格式转换的教程很多但真正能让你从“看得懂代码”变成“用得顺”的往往是那些代码之外的习惯。我在这里把多年攒下来的一些经验分享出来算不上什么高深技巧但确实能在关键时候救一下场。7.1 每一步处理都要能回溯我在 Notebook 里做数据处理时习惯把每个处理步骤单独放在一个 cell并且每步输出一次中间结果的行数、列数、缺失值数量。这样一旦发现最终结果不对可以回溯是哪个 cell 引入了问题。代码上也可以用复制 DataFrame 的方式保留快照df_raw df.copy() # 原始版本 df_no_dup df.drop_duplicates().copy() # 去重版本copy()一定不能省否则后续操作会连带修改前一个变量指向的同一块内存等到你发现“怎么原始数据也变了”时往往已经损失了可以回溯的起点。7.2 用“形状”和“缺失数量”做每一步的验收标准我不太信任自己直接看 10 行结果来判断数据处理是否正确更依赖一些量化指标。比如处理完缺失值后打印df.isna().sum().sum()处理完重复值后打印df.duplicated().sum()长宽转换后打印df.shape并与预期对比。这些数字不会骗人比肉眼瞥一眼靠得住。7.3 临时画图辅助判断数据清洗阶段就可以顺手画几个快速图不是最终版只是为了验证处理效果。比如清洗前后各画一个直方图重叠对比一下看分布有没有被意外改动长表转换后马上画一个折线图看分组曲线是否符合预期。这一步便宜又直观能省下到出整图时才发现数据有问题的返工成本。7.4 不要过度处理最后一条经验也是我见过新手最容易犯的把数据洗得“太干净”。比如强行把所有缺失值全部填掉、把所有异常值全部删掉、把所有列都转成数值型。真实科研数据里缺失和异常本身就是信息过度处理会引入偏见。该保留的缺失要保留该标记的异常要做标签而不是默默删除有些列就是应该保持 object 类型比如样品编号就算内容是数字前缀你把它转成 int 反而丢掉了语义。8. 数据处理在整个科研绘图流程中的位置图永远只是表达数据才是内容。画一堆漂亮的图但数据处理自欺欺人是科研中比较危险的操作反过来数据洗得干干净净哪怕图简单也是有价值的。如果这篇文章对你帮助不够具体你可以把它当一份“清单体”的备忘下一次打开 Pandas 准备处理数据时对着目录按图索骥。我个人在实际操作中的体会是数据清洗和格式转换这两个模块越早形成自己的固定套路后面科研产出就越顺。很多同学刚上手时觉得这里几个函数、那里几个参数东拼西凑也能出图但一旦项目数据规模变大分析流程变长没有一套规范流程支撑非常容易被自己之前的“灵机一动”拖垮。建议你按自己手头的数据把上面每一步实际操作一遍把代码拼成自己的一份“科研数据处理模板”之后再做新项目时直接套模板你会明显感觉轻松很多。再往下走如果你想深入可以研究怎么把 Pipeline 思想引入数据处理流程让每次清洗都变成可复用、可测试的步骤。但那已经是下一个阶段的进阶话题了。