第5章:5.3.3 字符向量元胞数组的综合练习(MATLAB入门课程)

​讲解视频:可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​

MATLAB教程新手入门篇(数学建模清风主讲,适合零基础同学观看)_哔哩哔哩_bilibili

在本节中,我们将一起完成三个综合性很强的文本数据处理案例。到目前为止,我们已经掌握了使用字符向量元胞数组来存储和处理多段文本的方法。这种方法在实际应用中能应对很大一部分问题,尤其是在 MATLAB 2016b 版本引入字符串类型之前,它就是处理文本数据的首选方法。利用我们迄今为止所学习的技能和函数,我们将展示如何有效地处理和分析文本数据,以解决实际遇到的问题。

由于我们还未学习如何导入本地的文本数据,例如txt格式的文本文件或Excel表格中的文本,所以我提前为大家准备好了相关的数据。大家只需要按照以下步骤操作,就能将数据加载到MATLAB的工作区中:

(1)下载本章的配套代码和资料(下载方法在配套的讲解视频),将下载好的压缩包解压,然后切换 MATLAB的当前文件夹到代码所在的文件夹(第二章中介绍了多种切换方法):

(2)确保在MATLAB的当前文件夹下能找到“data_5_3_3.mat”这个文件,然后使用下面的代码加载数据:load data_5_3_3.mat (后续章节会专门讲解导入数据相关的内容)

查看MATLAB工作区,如果出现了s1、s2和s3三个变量则代表成功了。

在视频教程中,我们会给大家简单讲解MATLAB中mat数据的保存和加载方法。

通过以上步骤,我们可以导入本小节三道题目所需的数据,接下来我们将深入探索如何利用这些数据来解决具体的文本处理和分析问题。

案例1变量s1是字符矩阵类型,s1中保存了五名同学的个人信息,包括他们的姓名、电话和邮箱信息,但这些信息的顺序并不一致。

请将每行信息重新格式化为“姓名, 电话号码, 电子邮件”的字符向量形式,并保存到元胞数组s中(注意,第二行有一个vip1的额外备注,这个备注不需要出现在s中);接下来使用换行符连接s中的各行,得到字符向量ss,并输出ss。

s = cellstr(s1);  % 转换为字符向量元胞数组
s = strip(s);  % 使用 strip 函数移除每个字符向量两端的空格
for ii = 1:length(s)  % 遍历元胞数组 s 中的每一个元素
    tmp = s{ii};  %获取当前同学的信息字符向量
    % 使用 strsplit 函数在空白字符处拆分字符向量
    tmp = strsplit(tmp);
    % 初始化一个新的元胞数组,用于存储姓名、电话号码和电子邮件
    new = cell(1,3);
    for jj = 1:length(tmp)  % 遍历拆分后的字符向量元胞数组tmp
        tmpjj = tmp{jj};
        % 判断当前字符向量是否全部为字母,如果是则视为姓名
        if all(isletter(tmpjj))
            new{1} = tmpjj;
        % 判断是否是电话号码,电话号码数字的长度为11,且不含'@'字符
        elseif sum(isstrprop(tmpjj,'digit')) == 11 && ...
                ~any(tmpjj == '@')  % 这里你也可以使用strfind函数判断
            new{2} = tmpjj;
        % 判断当前字符向量中是否包含'@'字符,如果有则视为电子邮件
        elseif any(tmpjj == '@')   % 也可以用~isempty(strfind(tmpjj,'@'))
            new{3} = tmpjj;
        end
    end
    % 使用 strjoin 函数将姓名、电话号码和电子邮件连接成一个字符向量
    s{ii} = strjoin(new,', ');
end
s
% 使用换行符将s中的元素连接起来,这样可以直接使用disp展示连接后的文本
ss = strjoin(s,newline);
disp(ss)

案例2假设你是你们班的班长,你需要了解班上同学的兴趣爱好,以便更好地组织活动和建立兴趣小组。现在你收集了40名同学的兴趣爱好信息,并记录在元胞数组 s2 中。s2中每个数据都是字符向量,包含由中文顿号分隔的多个兴趣爱好,部分同学的兴趣列表中包含了“其他”作为一个额外的选项。限于篇幅,下面展示了s2中前五名同学的兴趣爱好:

请你求出任意两名同学共有的兴趣爱好数量,并将结果保存到大小为40行40列的矩阵num中,其中num(ii, jj)表示第ii名同学和第jj名同学的共同爱好数量。显然,num是一个对称矩阵,即num(ii, jj) 和num(jj, ii)的结果相同。(注意:统计不同同学的兴趣爱好时,可以忽略“其他”这个选项,因为它可能涵盖的兴趣范围过于广泛)

n = length(s2);  % 同学的人数
% 初始化存储任意两名同学共有的兴趣爱好数量num矩阵
num = zeros(n);
for ii = 1:n  % 外层循环遍历每一位同学
    for jj = 1:ii  % 内层循环,jj不需要设置为1:n,要利用num矩阵的对称性 
        if ii == jj  % 如果是同一位同学,直接计算兴趣爱好的数量
           num(ii,jj) = length(strsplit(s2{ii},'、'));
        else
            % 否则,分别获取两位同学的兴趣爱好
            s_ii = strsplit(s2{ii},'、');
            s_jj = strsplit(s2{jj},'、');
            % 移除“其他”选项(这里使用strcmp函数生成的逻辑数组进行索引)
            s_ii(strcmp(s_ii, '其他')) = [];  
            s_jj(strcmp(s_jj, '其他')) = [];
            % 计算两位同学间共有的兴趣爱好数量
            p = length(intersect(s_ii,s_jj));
            % 将结果记录在num矩阵中的对应位置
            num(ii,jj) = p;
            num(jj,ii) = p;  % 别忘了给对称的位置元素赋值
        end
    end
end
disp(num) 

思考:在上面的代码中,每次比较两名同学的兴趣爱好时,都会重复执行strsplit函数来获取兴趣列表。你能否优化上面的代码来提高程序的运行效率?

案例3变量s3中保存着2023 年高教社杯全国大学生数学建模竞赛获奖名单,它是一个长度为59993的字符向量:

我们的目标是统计获得本科组一等奖的299支队伍中,各地区的数量和出现的频率。

请将结果保存到元胞数组c中,c有三列:第一列为各地区的名称,第二列为各地区获得本科组一等奖的队伍数量,第三列为相应的频率(即各地区获得一等奖的队伍数量除以获得本科组一等奖的所有队伍数量299)。此外,请根据获奖数量将元胞数组c按照降序排列,即获奖数量多的地区应排在前面,并将排序后的结果保存在元胞数组cc中。

s = strsplit(s3,newline);  % 在换行符处拆分字符向量s3
s = s';   % 拆分后的s是一个行向量,我们将其转置
ss = s(14:316)  % 本科组一等奖队伍(299支)所在的范围为s的14行至316行
% 注意,ss有303行,这是因为有些队伍的学校名称太长分成了两行,需要我们进行整理

下面我们对ss进行整理,将原本在同一行的数据重新合并为一行:

ydj = cell(299, 1);  % 初始化一个299x1的元胞数组保存一等奖的数据
jj = 0; % 新元胞数组的索引
for ii = 1:length(ss)
    tmp = ss{ii};
    % 检查当前行是否为新的队伍(注意到新的队伍总是以数字开头)
    if isstrprop(tmp(1), 'digit')
        jj = jj + 1;
        ydj{jj} = tmp; % 将当前行添加到ydj的新的一行
    else
        % 如果当前行不是新的队伍,则将其合并到前一行
        ydj{jj} = [ydj{jj}, tmp];
    end  
end
ydj

接下来,我们要统计各地区获得一等奖的数量和频率。下方给出的代码中用到了一个新的内置函数:tabulate函数,它能实现这个要求。

% 初始化保存地区的元胞数组
Region = cell(299,1);
% for循环遍历整理后的队伍信息元胞数组ydj
for ii = 1:299
    % 使用空格拆分每个队伍的信息
    teamInfo = strsplit(ydj{ii}, ' ');
    % 地区是拆分后数组的第三个元素
Region{ii} = teamInfo{3};
end
% tabulate函数需要统计和机器学习工具箱Statistics and Machine Learning Toolbox
% 题目要求的c就是tabulate函数返回的元胞数组
c = tabulate(Region)  
% 接下来,我们按照题目的要求,将元胞数组c按照获奖数量进行降序排列
cc = sortrows(c,2,'descend')
% 如果你忘了sortrows函数的话,也可以使用下面的方法进行排序:
[~,ind] = sort([c{:,2}],'descend') % [c{:,2}]取出c的第二列数据,并对其降序排列
cc = c(ind,:)  % 根据sort函数返回的索引ind调整行顺序
% 限于篇幅,下面仅展示c和cc两个元胞数组的前10行(第三列频率的单位为%)

拓展:tabulate函数

tabulate函数可用来生成数据的频数表,它能帮助我们快速统计数据中各个元素的出现次数和百分比。下面我们引用MATLAB官网的帮助文档来介绍它的用法:

tabulate(x) 显示向量 x 中数据的频数表。对于 x 中的每个唯一值,tabulate 函数显示该值在 x 中的实例数和百分比。

当 x 为数值时,tbl = tabulate(x) 以数值矩阵形式返回频数表 tbl,否则以元胞数组形式返回它。

注意:如果向量 x 只包含正整数,则 tabulate 将为 1 和 max(x) 之间没有出现在 x 中的整数返回 0 个计数。

使用tabulate函数需要统计和机器学习工具箱Statistics and Machine Learning Toolbox,你可以在MATLAB的命令行窗口输入   ver   这个命令,它可以查看你已经安装的工具箱。

如果你没有安装对应的工具箱,可以卸载现有的MATLAB后重新安装一次。安装过程中,有一个环节是选择需要安装的产品(工具箱)。有些同学电脑硬盘空间有限,我这里提供了一个不需要安装的产品列表,大家可以在微信公众号《数学建模学习交流》中查看历史文章(发布时间是 2021 年 7 月 15 日)。下面我们来看tabulate函数的例子:

在上表最后一个例子中,向量 x3中的元素都是正整数,此时输出结果中的Value范围为1至x3的最大值5,由于数字1和3出现的次数为0,因此对应的Count和Percent都为0。如果你不希望在结果中看到频数为0的情况,你可以在调用 tabulate函数之前,将元素全为正整数的向量 x3 转换为字符串类型(下一节介绍)或者分类变量类型(后续章节介绍)。

现在考考大家,如果你的MATLAB没有安装统计和机器学习工具箱,你能通过其他方式来实现tabulate函数的功能吗?(假设你要计算的数据是字符向量元胞数组类型)

x = {'yes','no','yes','yes','and','no','or'};
% 使用unique函数找出x中的唯一值,同时获得每个元素在唯一值数组中的索引ind
[Value, ~, ind] = unique(x, 'stable'); % 加'stable'保持原文本出现的先后顺序
Count = sum(ind == 1:length(Value)); % 计算每个唯一值在x中出现的次数
Percent = Count/sum(Count)*100;  % 计算每个唯一值出现的百分比
t = cell(length(Value),3); % 初始化一个元胞数组用于保存最终结果
% 将Value、Count和Percent的值分别填充到元胞数组t的相应位置
t(:,1) = Value;  % 第一列保存唯一值(这里可以不对Value转置,大小兼容就行)
t(:,2) = num2cell(Count);  % 第二列保存每个值的计数
t(:,3) = num2cell(Percent);  % 第三列保存每个值的百分比
disp(t)  % 输出结果t
% 大家可以对比tabulate函数和我们自己计算的结果是否相同
t2 = tabulate(x)

  点击下方的CSDN专栏阅读下一篇文章:

MATLAB入门课程专栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值