ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB批量读取Excel并自动绘图:数据清洗+可视化闭环

MATLAB批量读取Excel并自动绘图:数据清洗+可视化闭环 简介本资源是一份面向MATLAB初学者与数据处理实践者的实用脚本工具聚焦Excel批量读取、无效数据清洗及自动化绘图三大核心需求适用于课程设计、科研数据预处理及工程报告可视化等场景。压缩包为ZIP格式仅含1个关键文件——data_treating.m函数脚本718B该文件封装了可指定Sheet编号的xlsread批量读取逻辑、基于isnan/isnumeric的空值与非数值内容识别与NaN替换机制以及列筛选与plot绘图一体化流程支持灵活调用与快速二次开发。目前已有2122人学习下载体现了其在轻量级自动化办公脚本领域的高频实用价值。读者可直接部署运行获得从原始Excel到结构化图表的一站式处理能力并基于源码理解MATLAB中文件遍历、条件清洗、矩阵索引与图形标注等关键编程范式。1. 用 MATLAB 批量读取 Excel 表格并自动绘图跳过空行、识别错位标题、过滤非数值列一线工程师每天都在跑的「数据清洗可视化」闭环你刚收到市场部发来的 37 个 Excel 文件每个文件含 5 张表“日销售”“渠道明细”“退货记录”“库存快照”“客户反馈”但命名不统一有的叫“日销.xlsx”有的叫“sales_daily_202405_v2.xlsx”部分表格首行是合并单元格标题第 3 行才是真实列名还有几份数据里混着“暂无”“/”“N/A”甚至整列是文字说明。手动打开、复制、粘贴、补缺失值、再画折线图别试了——MATLAB 的readtabledirplot组合拳12 行核心代码就能完成从文件扫描到多子图输出的全流程。这不是脚本玩具而是产线质量监控、金融日报生成、科研实验复现中真正落地的标准化数据流水线。适合需要处理 10500 个 Excel 文件、且对数据鲁棒性比如空单元格、类型错乱、列顺序变动有硬性要求的工程师、分析师和研究生。它不依赖 Excel 应用程序进程不触发 COM 接口超时也不需要提前安装任何加载项。2. 构建可扩展的批量读取框架用dir筛选 readtable自适应解析 结构体封装原始数据2.1 按业务规则精准定位 Excel 文件支持通配符、路径递归与时间过滤批量处理的第一步不是读数据而是精准找到该读的文件。dir函数远比uigetdir或硬编码路径可靠。假设所有待处理文件存放在D:\data\monthly_reports\下且只需处理 2024 年 5 月之后的.xlsx文件% 定义根目录与搜索模式 root_dir D:\data\monthly_reports\; search_pattern **\*20240[5-9]*.xlsx; % 匹配 202405 到 202409 的文件名 all_files dir(fullfile(root_dir, search_pattern)); % 过滤掉子目录只保留文件 excel_files {all_files([all_files.isdir] 0).name}; full_paths fullfile({all_files([all_files.isdir] 0).folder}, excel_files); % 按修改时间升序排列确保按时间顺序处理 file_times [all_files([all_files.isdir] 0).datenum]; [~, idx] sort(file_times); excel_files excel_files(idx); full_paths full_paths(idx);提示**\启用递归搜索避免手动遍历子文件夹datenum获取修改时间戳比datestr更易排序[all_files.isdir] 0是 MATLAB 中筛选文件的标准写法比isfile在旧版本中更兼容。2.2 用readtable的高级选项应对 Excel 布局混乱跳过空行、定位真实标题、自动类型推断Excel 表格结构千差万别readtable的默认行为常失败。关键参数必须显式设置% 预定义要读取的工作表名列表按业务优先级 target_sheets {日销售, sales_daily, Daily_Sales, Sheet1}; % 多种可能名称 all_data struct(); % 存储每个文件的解析结果 for i 1:length(full_paths) fprintf(正在处理: %s\n, excel_files{i}); % 尝试读取第一个匹配的工作表 sheet_name ; for j 1:length(target_sheets) try % 关键参数详解 % - ReadVariableNames: true → 从第一行读列名但需配合 HeaderLines % - HeaderLines: 2 → 跳过前2行合并标题空行第3行作为列名 % - EmptyFieldRule: auto → 自动将空单元格转为 missing % - DatetimeType: datetime → 强制日期列解析为 datetime 类型 % - TextType: string → 文本列统一为 string避免 char 数组截断 tbl readtable(full_paths{i}, ... Sheet, target_sheets{j}, ... ReadVariableNames, true, ... HeaderLines, 2, ... EmptyFieldRule, auto, ... DatetimeType, datetime, ... TextType, string); sheet_name target_sheets{j}; break; catch ME continue; % 尝试下一个工作表名 end end if isempty(sheet_name) warning(文件 %s 未找到有效工作表跳过, excel_files{i}); continue; end % 封装为结构体字段保留原始文件名与工作表名 all_data.(excel_files{i}) struct(table, tbl, sheet, sheet_name, path, full_paths{i}); end注意HeaderLines参数是解决“标题在第 N 行”的核心它比手动readmatrixreadcell拼接更安全EmptyFieldRule设为auto后readtable会将空单元格、#N/A、NULL统一转为missing后续可用fillmissing统一处理TextType必须设为string否则中文列名或长文本会被截断为 char 数组。2.3 将异构表格统一为标准结构体数组列名标准化 数据类型校验不同 Excel 文件的列名可能为“销售额”“SALES_AMT”“amount_yuan”需映射到统一字段。同时校验数值列是否真为数值% 定义业务字段映射表key标准名value可能的原始列名 field_mapping containers.Map(... {sales, date, product_id, channel}, ... {{销售额,SALES_AMT,amount_yuan}, ... {日期,DATE,sale_date}, ... {产品编码,PROD_ID,item_no}, ... {渠道,CHANNEL,sales_channel}}); % 对每个文件执行标准化 for file_name fieldnames(all_data) tbl all_data.(file_name{1}).table; % 步骤1重命名列模糊匹配 new_vars {}; for std_field keys(field_mapping) candidates field_mapping(std_field); found false; for k 1:length(candidates) % 不区分大小写、忽略空格和下划线的近似匹配 pattern regexprep(lower(candidates{k}), [ _], ); for j 1:width(tbl) col_test regexprep(lower(tbl.Properties.VariableNames{j}), [ _], ); if contains(col_test, pattern) || contains(pattern, col_test) new_vars{j} std_field; found true; break; end end if found, break; end end if ~found, new_vars{end1} []; end % 未匹配列置空 end % 步骤2提取必需列强制类型转换 required_cols {sales, date, product_id, channel}; valid_tbl table(); for col required_cols if ismember(col, new_vars) idx find(ismember(new_vars, col)); col_data tbl{:,idx}; % 类型强转sales→doubledate→datetime其余→string switch col case sales valid_tbl.(sales) double(col_data); case date valid_tbl.(date) datetime(col_data, InputFormat, auto); otherwise valid_tbl.(col) string(col_data); end else % 缺失列填充默认值 switch col case sales valid_tbl.(sales) NaN(height(tbl), 1); case date valid_tbl.(date) datetime(NaT, Format, yyyy-MM-dd); otherwise valid_tbl.(col) repelem(unknown, height(tbl), 1); end end end all_data.(file_name{1}).standardized valid_tbl; end提示containers.Map实现灵活映射避免硬编码strcmpiregexprep清洗列名空格和下划线提升匹配鲁棒性datetime(..., InputFormat, auto)能自动识别2024/05/01、01-May-2024、20240501等格式对缺失列填充NaN或NaT保证后续plot不报错。3. 无效内容的三层防御机制缺失值插补、异常值剔除、逻辑矛盾校验3.1 第一层用fillmissing智能填充数值列拒绝简单均值填充直接fillmissing(tbl.sales, mean)会污染趋势分析。应按业务周期分组填充% 按日期分组用同周内其他天的均值填充更符合销售场景 if ~isempty(all_data) for file_name fieldnames(all_data) tbl all_data.(file_name{1}).standardized; % 添加星期几列用于分组 tbl.weekday weekday(tbl.date, long); % 按 weekday 分组用组内均值填充 sales 缺失 tbl.sales fillmissing(tbl.sales, groupmean, GroupingVariables, tbl.weekday); % 移除辅助列 tbl.weekday []; all_data.(file_name{1}).standardized tbl; end end注意groupmean比linear更符合业务逻辑周一销量通常接近而非线性过渡GroupingVariables参数必须传入变量名或向量不能传字符串weekday。3.2 第二层用 IQR 法识别并标记异常销售值保留原始数据可追溯异常值不直接删除而是标记为NaN并记录原因便于审计function [cleaned_sales, outlier_log] detect_sales_outliers(sales_vec, date_vec) % 计算季度滚动 IQR比全局 IQR 更敏感 q_start min(date_vec); q_end max(date_vec); q_len calmonths(q_end, q_start); cleaned_sales sales_vec; outlier_log table(Size, [0,3], VariableTypes, {datetime,double,string}, ... VariableNames, {Date,Value,Reason}); for i 1:length(sales_vec) if isnan(sales_vec(i)), continue; end % 取前后 90 天窗口内的数据排除自身 window_mask (date_vec date_vec(i)-days(90)) ... (date_vec date_vec(i)days(90)) ... (date_vec ~ date_vec(i)); window_data sales_vec(window_mask); if length(window_data) 5, continue; end % 窗口数据不足跳过 Q1 prctile(window_data, 25); Q3 prctile(window_data, 75); IQR Q3 - Q1; lower_bound Q1 - 1.5*IQR; upper_bound Q3 1.5*IQR; if sales_vec(i) lower_bound || sales_vec(i) upper_bound outlier_log [outlier_log; table(date_vec(i), sales_vec(i), ... sprintf(IQR超出 %.1f-%.1f, lower_bound, upper_bound))]; cleaned_sales(i) NaN; end end end % 在主循环中调用 for file_name fieldnames(all_data) tbl all_data.(file_name{1}).standardized; [tbl.sales, log] detect_sales_outliers(tbl.sales, tbl.date); all_data.(file_name{1}).outlier_log log; all_data.(file_name{1}).standardized tbl; end提示滚动窗口法rolling window比静态 IQR 更适应季节性波动prctile计算分位数比quantile在旧版 MATLAB 中更稳定记录outlier_log表方便后续导出为outliers_report.xlsx。3.3 第三层跨字段逻辑校验拦截明显错误组合例如sales 0但date为NaT或channel为空但product_id有效% 定义校验规则返回逻辑向量true 表示该行需标记为可疑 function flag cross_field_validation(tbl) flag false(height(tbl), 1); % 规则1销售为正但日期缺失 flag flag | (tbl.sales 0 isnat(tbl.date)); % 规则2渠道为空但销售非零 flag flag | (ismissing(tbl.channel) ~isnan(tbl.sales) tbl.sales ~ 0); % 规则3产品ID含非法字符仅数字和字母 if ~isempty(tbl.product_id) invalid_id ~cellfun((x) isempty(regexp(x, [^a-zA-Z0-9])), tbl.product_id); flag flag | invalid_id; end end % 执行校验并添加标记列 for file_name fieldnames(all_data) tbl all_data.(file_name{1}).standardized; tbl.validation_flag cross_field_validation(tbl); all_data.(file_name{1}).standardized tbl; end注意isnat专用于判断datetime是否为NaTcellfunregexp处理 string 列的正则校验validation_flag列可后续用于findgroups分组统计问题率。4. 高效批量绘图用tiledlayout统一布局 plot自动适配多数据源 中文标签防乱码4.1 用tiledlayout创建自适应网格避免subplot的坐标轴重叠问题% 计算所需子图数量每个文件一个图最多 6 行×4 列 n_files length(fieldnames(all_data)); n_rows min(6, ceil(n_files / 4)); n_cols min(4, n_files); fig tiledlayout(n_rows, n_cols, TileSpacing, compact, Padding, none); title(fig, 各月销售趋势对比已过滤异常值, FontSize, 12, FontWeight, bold); for i 1:n_files file_name fieldnames(all_data){i}; tbl all_data.(file_name).standardized; % 过滤掉 validation_flag 为 true 的行 valid_mask ~tbl.validation_flag; plot_data tbl(valid_mask, :); % 按日期排序确保折线连续 plot_data sortrows(plot_data, date); % 创建子图 nexttile; h plot(plot_data.date, plot_data.sales, -o, MarkerSize, 3, LineWidth, 1.2); % 设置标题与标签支持中文 title(sprintf(%s (%d条), file_name, height(plot_data)), FontSize, 9); xlabel(日期, FontSize, 8); ylabel(销售额, FontSize, 8); % 旋转 x 轴标签避免重叠 xtickangle(gca, -30); grid on; end提示tiledlayout是 R2019b 推荐方案TileSpacing和Padding参数可消除子图间白边sortrows(..., date)确保plot不因日期乱序而画出交叉线xtickangle直接旋转刻度标签比datetick更可控。4.2 解决 MATLAB 绘图中文乱码三步永久生效配置乱码根源是默认字体不支持中文。以下配置一次永久生效% 步骤1查询系统中可用的中文字体Windows 常见 available_fonts system(fc-list :langzh); % Linux/macOS % Windows 下常用SimHei黑体、Microsoft YaHei微软雅黑、KaiTi楷体 % 步骤2设置图形默认字体影响所有后续 figure set(groot, DefaultAxesFontName, SimHei); set(groot, DefaultTextFontName, SimHei); set(groot, DefaultLegendFontName, SimHei); % 步骤3设置字号避免过小 set(groot, DefaultAxesFontSize, 10); set(groot, DefaultTextFontSize, 10); % 验证新建 figure 测试 test_fig figure(Name, 中文测试); ax axes(test_fig); plot(ax, 1:5, rand(1,5), -o); title(ax, 中文标题正常显示); xlabel(ax, X轴中文标签); ylabel(ax, Y轴中文标签);注意groot是图形根对象set(groot, ...)影响所有新创建的 figurefc-list命令在 Linux/macOS 中有效Windows 用户可改用system(powershell -Command [System.Drawing.Text.FontFamily]::Families | ForEach-Object Name)若SimHei不可用尝试Microsoft YaHei。4.3 导出高清图与数据摘要一键生成 PDF 报告 CSV 清洗日志% 导出当前 tiledlayout 为 PDF矢量图缩放不失真 exportgraphics(fig, sales_trends_report.pdf, ContentType, vector); % 生成清洗摘要 CSV summary_data table(Size, [0,5], VariableTypes, {string,double,double,double,string}, ... VariableNames, {FileName,RawRows,CleanedRows,OutlierCount,ValidationFailRate}); for i 1:n_files file_name fieldnames(all_data){i}; raw_rows height(all_data.(file_name).table); clean_rows height(all_data.(file_name).standardized); outlier_count height(all_data.(file_name).outlier_log); fail_rate mean(all_data.(file_name).standardized.validation_flag); summary_data [summary_data; table(file_name, raw_rows, clean_rows, outlier_count, ... sprintf(%.1f%%, fail_rate*100))]; end writematrix(summary_data, data_cleaning_summary.csv, Delimiter, ,); % 导出所有清洗后数据为单个 Excel每文件一个 sheet cleaned_tables {}; sheet_names {}; for i 1:n_files file_name fieldnames(all_data){i}; tbl all_data.(file_name).standardized; % 移除 validation_flag 列仅用于过程标记 tbl.validation_flag []; cleaned_tables{i} tbl; sheet_names{i} strrep(file_name, .xlsx, ); % 去掉扩展名作 sheet 名 end writecell({文件名, 原始行数, 清洗后行数, 异常值数, 校验失败率}, report_summary.xlsx); xlswrite(report_summary.xlsx, summary_data{:,:}, Summary); for i 1:length(cleaned_tables) writematrix(cleaned_tables{i}, report_summary.xlsx, Sheet, sheet_names{i}); end提示exportgraphics(..., ContentType, vector)输出 PDF 为矢量图插入 Word/PPT 无限缩放writematrix写入 CSV 比writetable更快且无引号包裹xlswrite已被writematrix/writetable替代但writematrix不支持多 sheet故此处仍用xlswriteR2019a 兼容。5. 生产环境加固技巧用try-catch包裹关键步骤 日志文件记录全过程 内存优化策略5.1 用结构化日志替代fprintf记录时间戳、操作、状态、耗时function log_event(log_file, event_type, message, duration_sec) timestamp datetime(now, Format, yyyy-MM-dd HH:mm:ss.SSS); log_entry sprintf(%s | %s | %s | %.3f sec\n, ... string(timestamp), event_type, message, duration_sec); fid fopen(log_file, a); fwrite(fid, log_entry, char); fclose(fid); end % 在主流程中调用 log_file batch_processing_log.txt; log_event(log_file, START, 开始批量处理, 0); tic; % ... 执行文件扫描 ... toc_val toc; log_event(log_file, INFO, sprintf(扫描到 %d 个 Excel 文件, length(full_paths)), toc_val); tic; % ... 执行读取与清洗 ... toc_val toc; log_event(log_file, INFO, 完成数据清洗, toc_val); % 最终记录 log_event(log_file, END, 全流程结束, 0);注意datetime(..., Format, ...)精确到毫秒fopen(..., a)追加写入避免覆盖历史日志日志文件名固定便于运维监控。5.2 大文件内存优化用readtable的Range参数分块读取当单个 Excel 超过 10 万行时全量读取易爆内存。改用分块function tbl_chunk read_large_excel(filename, sheet, chunk_size) % 先获取总行数不读数据 info spreadsheetInfo(filename, sheet); total_rows info.LastRow; tbl_chunk table(); start_row 3; % 跳过标题行 while start_row total_rows end_row min(start_row chunk_size - 1, total_rows); range_str sprintf(A%d:%c%d, start_row, char(64 info.LastColumn), end_row); try chunk readtable(filename, Sheet, sheet, Range, range_str, ... ReadVariableNames, true, HeaderLines, 2); tbl_chunk [tbl_chunk; chunk]; catch ME warning(读取范围 %s 失败: %s, range_str, ME.message); end start_row end_row 1; end end % 调用示例仅对 50000 行的文件启用 if info.LastRow 50000 tbl read_large_excel(full_paths{i}, sheet_name, 10000); else tbl readtable(...); % 原逻辑 end提示spreadsheetInfo返回工作表元数据不加载数据Range参数格式为A1:C1000需动态计算列字母char(64 n)将列号转为字母A1, B2...。5.3 错误恢复机制保存中间状态失败后可续跑% 在循环前检查是否存在中间状态文件 state_file processing_state.mat; if exist(state_file, file) load(state_file); fprintf(检测到中断状态从文件 %d 继续\n, resume_idx); else resume_idx 1; end % 主循环中 for i resume_idx:length(full_paths) try % ... 处理逻辑 ... % 成功后更新状态 save(state_file, i, all_data, log_file); catch ME % 记录错误并保存当前进度 log_event(log_file, ERROR, sprintf(文件 %s 处理失败: %s, excel_files{i}, ME.message), 0); save(state_file, i, all_data, log_file); warning(已保存中断状态可重新运行继续); break; end end注意save保存变量名而非值i记录当前索引exist(..., file)是跨平台检查文件存在的标准方法break退出循环而非return确保后续清理代码执行。本文还有配套的精品资源点击获取
返回列表