分析医学背景下的线性分类器混淆矩阵。
Jules Bss 在 Unsplash 上的照片
构建分类模型时,最具挑战性的任务是评估性能。广泛使用的一个基本指标是混淆矩阵。在本教程中,我将尝试解释混淆矩阵和正确的阅读方法。
覆盖的是什么?
- 混淆矩阵结构和术语。
- 一类和二类错误。
- 准确性悖论。
- 精确度和召回率。
- 累积精度曲线— CAP 分析
- 受试者工作特征— ROC 分析
- 曲线下面积——AUC
- 延伸阅读—发表的学术论文。
1.什么是混淆矩阵?
它是一个维数为 N*N 的矩阵,允许从分类模型中提取有意义的度量,这有助于评估性能。它被视为预测质量的总结报告。
怎么读?
混淆矩阵的水平轴代表实际值,而垂直轴代表预测值。例如,在下面演示的 2X2 混淆矩阵中,有两个数据类;阳性病例(1)和阴性病例(0)。模型的阳性预测可能是真阳性或假阳性情况。真正的阳性预测(TP)定义为模型对阳性病例进行正确分类的次数。相比之下,假阳性预测(FP)是模型对阳性病例进行错误分类的次数。同样,真正的负面预测(TN)被定义为模型对负面情况进行正确分类的次数。相反,假阴性预测(FN)是模型对阴性病例进行错误分类的次数。
混乱矩阵解剖学
正如你可能注意到的,正确的预测落在矩阵对角线上。
FP 也被定义为 I 型错误,它表示我们的模型错误地预测了多少个阴性病例为阳性病例。FN 被认为是第二类错误,它表明我们的模型错误地预测了多少阳性病例为阴性病例。
从矩阵中提取度量
可以使用几个重要的度量来评估该模型。我们将一个一个地检查它们。让我们从基本的开始。
分类预测模型产生的混淆矩阵示例
- 准确性:分类器正确的频率。是对角线值之和与总例数之比:(TP+TN)/(TP+TN+FP+FN)—1500/1650 = 0.91。
- 错误率:所有错误预测与总案例之间的比率。误差率范围从 0 到 1;最好的是 0,而最差的是 1。(TN)/(TP+TN+FP+FN)—500/1650 = 0.30。
- 准确率:所有正面预测与总案例之间的比率。最好的准确率是 1,最差的是 0。(TP)/(TP+TN+FP+FN)—1000/1650 = 0.61。
- 特异性(阴性率):真阴性与真阴性和假阳性之和的比率。(TN)/(TN+FP) — 500/600 = 0.80。
然而,仅仅考虑准确性不足以恰当地评估模型。一个模型可能有很高的精确度,但在预测中并不可靠,这是由于类别不平衡造成的。因此,除了准确性之外,还有其他用于评估的方法,如精确度和召回率。
- 精确度:有多少被预测为阳性的结果是真正的阳性病例。要确定模型精度,请计算真阳性与真阳性和假阴性之和之间的比率。(TP) / (TP + FP) — 1000/1100 = 0.90。
- 回忆(敏感度):假设案例确实是阳性的,那么案例是阳性的频率。(TP) /(TP + FN) — 1000/1050 = 0.95。
在这种情况下,我们可以将结果解读为:预测类的 90% **(精度)原来是 1(正)。而 95%的(回忆)**阳性类别被模型成功预测。
精准与召回
使用哪一种取决于应用程序和您想要的结果类型。您可能已经注意到, **Precision 使用 FP(第一类错误)**而 Recall 使用 FN(第二类错误)。根据经验,当 FP 或 I 类误差比 II 类误差更重要时,使用 Precision。相反,当假阴性比假阳性更重要时,使用回忆,尤其是在医学研究中。
**Precision 使用 FP(第一类错误)**而 Recall 使用 FN(第二类错误)
如前所述,由于准确性悖论,我们需要更多的方法来评估我们的模型。累积精度曲线 CAP 和接收器工作特性曲线 ROC 就是这些方法中的一种。
2.累积精度曲线上限
通过将曲线与直接获得最大数量正面结果的完美上限和正面结果平均分布的随机上限进行比较,上限可用于评估模型。维基百科
准确率——曲线下的面积,定义为生成模型下的面积与完美模型下的面积之比(见下图)。该比率范围在 0 和 1 之间,越接近 1 的模型越精确。例如,下面的模型假设联系的客户数量和退货销售之间存在关系。计算返回最大购买量的最小顾客数是一个优化问题。如图所示,好的模型(绿线)越接近完美的模型(蓝线)就越好。
累积精度曲线— CAP
这些曲线可以根据下表进行评估:
评估上限时应遵循的假设规则
3.接收机工作特性曲线 ROC
累积精度曲线上限与接收器工作特性曲线 ROC不同。
受试者工作特征(ROC)曲线用于更广泛的领域,并经常用于医学研究。ROC 是一种基于两个因素评估模型性能的方法:特异性和敏感性。正如我们前面提到的,敏感性是真阳性率,特异性是假阴性率。ROC 曲线在 y 轴上代表灵敏度,在 x 轴上代表(1-特异性)。每个轴从 0 开始,到 1 结束。
ROC 曲线
更详细地说,假设有三个不同的模型。两个分布之间的重叠区域代表 FN 和 FP,如下图所示。第一个模型代表了一个完美的分类器(完美的分类——没有重叠)。
完美分类器
第二个模型也被认为是一个好模型。曲线下的面积(AUC)很小,且分布均匀。
好的分类器
然而,最后一个模型是预测模型的坏例子,并且被认为在区分 class_1 和 class_2 方面是弱的。
错误的分类器
曲线下面积或 AUC 是用于评估分类器的基本度量,特别是对于不平衡的类别,这是使用 ROC 的一个相当大的好处。
外卖食品
- 在敏感性和特异性之间应该有一个平衡;否则,模型会陷入过度拟合的陷阱。
敏感性和特异性的关系
- ROC 曲线的解释与累积精确度曲线(CAP)相同。只要模型处于向上的正确方向,它就表现得更好。
- 重要的是要注意,具有有意义的性能水平的分类器通常位于随机(基线)和完美线之间的区域。
- 帽子不摇。
- FN(假阴性)是第二类错误,而 FP(假阳性)是第一类错误。
- 如果第二类错误对你的模型更重要,使用召回措施;否则使用 type-I 错误。
- 正确的预测落在混乱矩阵的对角线上。
- 该模型不能仅基于准确性进行评估,它可能会由于类别不平衡而产生误导。例如,考虑 ROC AUC 曲线。
参考
最后,我希望我的帖子能帮助你理解模型评估的不同方法,以及你应该考虑哪一种。感谢阅读!!
分析 YouTube 话语并使用 Python 找到巨魔机器人
使用 YouTube 数据 API 分析 YouTube 评论中的政治话语并识别机器人
正如你在选举年可能会预料到的那样,社交媒体如今充斥着政治话语——其中一些合理且富有成效,许多具有煽动性。关于社交媒体的两极分化“回音室”效应,以及有针对性的虚假信息运动和假新闻,已经有大量文章发表。为了研究 YouTube 上的政治话语,我将解释如何使用免费的 YouTube 数据 API 将 YouTube 评论收集到一个有趣的数据集中。然后,我们将使用一些数据科学工具,如 Pandas 和 Plotly,来可视化这个数据集并寻找模式。YouTube 上的数据量是巨大的,所以我将这个项目的代码开源,以鼓励其他人也进行他们自己的分析。如果你愿意,请跟随代码!
由 Kon Karampelas 在 Unsplash 上拍摄的照片
使用 YouTube 数据 API 收集数据
YouTube 数据 API 允许每天多达 10,000 个请求免费检索 YouTube 视频、播放列表、评论和频道的信息。这些都可以在 YouTube 上公开获得,但是 API 使得检索变得更加容易。第一步是生成自己的个人 API 密匙。您将需要使用它来替换附带代码中的占位符,以便发出 API 请求。
在我的程序中,您指定要抓取的频道列表,每个频道要抓取多少个视频,每个视频要抓取多少页评论。(就我的分析而言,我想获得主流政治新闻频道与各种意识形态倾向的混合,如 CNN、MSNBC 和福克斯新闻频道。)程序然后处理 API 请求,这些请求返回包含所请求数据的 JSON 响应。经过一点解析后,这个数据可以附加到 Pandas 数据帧中,当最后一个 API 请求完成时,这个数据帧可以保存到 CSV 文件中。如果我们收集更多的数据,可以在以后更新这个 CSV 文件,并且当我们想要运行分析时,可以将它加载回数据帧。以下是运行一些收集后的示例(请注意,我们在这里筛选的唯一内容是频道,我们没有专门选择政治评论):
我们的数据库包含成千上万的评论和它们的元数据。我们希望确保每条评论都能追溯到发布它的视频、发布它的用户以及其他相关信息,如赞数和时间戳。如你所见,有很多政治性的内容。图片作者。
目前,我也在探索更多的小众政治渠道,看看它们与大网络有什么不同。我鼓励你尝试从各种渠道收集数据——可能是你订阅的渠道,也可能是 YouTube 算法向你推荐的渠道。
基本分析:关键字搜索和喜欢/不喜欢细分
让我们实现一个基本关键字搜索,它将调出包含给定搜索词的评论。Pandas 提供的 DataFrame 抽象使得按频道名称过滤变得非常容易,比如计数和其他特性。
# select the rows that contain the query string (case insensitive)
subset = df[df["textOriginal"].str.contains(query, case=False)]
以下是“covid”的两个不同结果,显示了对新冠肺炎疫情政治的高度分歧的世界观:
美国最大的威胁。Don the Con,双手沾满了 190,000 新冠肺炎人的鲜血,并谎称其对美国公众构成威胁。
疾控中心说,COVID 只杀了 9200 人。其他死于 COVID 的人都患有严重的疾病,或者死于完全不同的疾病,只是测试呈阳性。现在结束所有的封锁!
接下来,我们将生成一个按时间顺序排列的视频条形图,显示他们喜欢/不喜欢的细目分类。为此,我们必须返回到 YouTube 数据 API 来检索评论数据库中每个唯一视频的完整数据(这是因为 CommentsThread 请求的 API 响应提供了每个评论视频的 videoID,但没有提供附加信息,如喜欢和不喜欢的数量)。我确定的实现是创建一个单独的视频数据库,它有自己相应的数据帧和 CSV 文件,并给它一个同步功能,这将确保它有评论数据库中出现的所有视频的信息。
可视化一组最近的福克斯新闻频道视频的喜欢/不喜欢比率。我真的很好奇那个令人厌恶的视频是什么!图片作者。
为了制作如上图所示的条形图,我使用了 Plotly Express,它的bar()
功能使得创建美观的交互式图表变得非常容易:
# take video database's DataFrame: sort, filter, plot
df = vdb.df.sort_values(by="publishedAt")
if channelName:
df = df[df["channelName"] == channelName]
fig = px.bar(df, x="videoId", y=["likes", "dislikes"], hover_data=["channelName", "title", "publishedAt"])
fig.show()
中级分析:word2vec 和 t-SNE
关键词搜索很棒,但它应该会让你觉得我们几乎没有挖掘 10 万条评论的潜力。让我们用单词嵌入来提升我们的分析,特别是 word2vec 。
顾名思义,word2vec 将单词转换成高维向量。这样做的主要优点是,以相似方式使用的单词将被转换成相似的向量(具有高余弦相似度的向量)。这样,具有相似含义的不同词汇标记——像“GOP”和“Republicans”——就可以映射到 100 维超空间中的相似位置(听起来像科幻,我知道)。这个方法允许我们在数据集中找到更有趣的模式,幸运的是,gensim 中有一个 Python 实现。
虽然对人类来说,可视化高维多维空间是不可能的,但幸运的是,有一些方法可以将这些高维嵌入物投射到二维或三维空间中,这样我们就可以将它们包裹起来。 t-SNE 方法 将word 2 vec 向量转换成我们可以绘制和查看的东西,关键约束是它近似保持距离关系。所以超空间中距离较远的向量在 t-SNE 之后依然会很远,原本很近的向量依然会很近。
我们可以使用 word2vec 和 t-SNE 来绘制彼此相似的向量簇。关于这方面的一个很棒的教程,请查看这篇文章。给定一个单词列表,我们根据单词的嵌入性绘制出与它们最相似的单词。请注意,相似性是根据单词是否在相似的上下文中使用来定义的,因此聚类可能不总是包含语义同义词。
根据福克斯新闻频道视频的评论训练单词嵌入,使用 t-SNE 投影到二维空间。显示类似于“biden”和“blm”(即“黑人生命至关重要”)的单词。这表明评论区对 BLM 有相当负面的看法。图片作者。
根据 MSNBC 视频的评论训练单词嵌入,使用 t-SNE 投影到二维空间。显示类似于“trump”和“gop”的单词。图片作者。
矢量化并绘制整个注释
虽然绘制单个单词很有见地,但如果能够绘制整个评论,使相似的评论彼此靠近,那将会很有用。由于评论是一系列单词,我们可以尝试的一件事是对评论中所有单词的单词向量进行平均,然后绘制这个向量来表示整个评论。然而,这将使修辞学过分重视像“the”或“and”这样的普通词。为了解决这个问题,我们可以执行一个 Tf-Idf 矢量化,并在取平均值时使用逆文档频率来加权评论中的单词。这使得生僻字比普通字具有更高的权重,在 Scikit-Learn 中有一个易于使用的实现。这种方法有缺点,但它应该开始抓住 YouTube 评论的一些意义和主题。
密切相关的评论的群集(图中间的红点)。一查,都是类似“特朗普撒谎,美国人死了!”。重要的是,尽管在措辞上有一些小的不同,它们还是聚集在一起,所以评论矢量化工作正常。图片作者。
狩猎巨魔机器人
你可能听说过某些国家行为者试图通过巨魔军队和自动评论机器人来影响美国大选。我想看看是否有一种方法可以完全基于我们的评论数据库来自动检测可能的机器人活动。
有几件事我建议作为机器人活动的危险信号。一个是在短时间内发布大量评论。这要么表明一个人正在复制和粘贴评论,而不是逐字逐句地键入它们,要么表明一个自动化的机器人在工作。另一个危险信号是一个账户在多个不同的视频上发布了完全相同的评论。
使用我们的数据库,我们可以很容易地筛选给定频道上最多产的评论者,并绘制他们的评论活动。通过绘制以 10 分钟为增量的评论活动,我注意到一些帐户的活动对一个人来说非常不寻常——比如在五分钟内评论十几次多段评论。一个账号在 10 分钟内发布了 60 次完全相同的评论:
“全国范围内的内乱共和党下降 33%失业率增加两倍,19 万美国人死亡。特朗普让你的生活越来越糟糕”
有趣的是,该账户发布了同一条评论的细微变化,不同之处仅在于评论末尾多了几个标点符号。这让我怀疑该机器人被编程为逃避 YouTube 的机器人检测措施。在下面的可视化中,x 轴被分割成 30 分钟的时间增量,每个堆叠块代表同一帐户的一个评论。这些块通过视频进行颜色编码。
在短时间内(不到 90 分钟),福克斯新闻频道 YT 频道的各种视频中,一个账户有几十条反特朗普的评论。突出的评论是:“五次逃避兵役的骨刺学员,充满激情地憎恨美国军队……”作者图片。
在福克斯新闻频道 YT 频道的各种视频中,一个账户(不是上面的同一个账户)发表了几十条支持川普的评论。突出显示的评论是:“巴尔的摩马里兰州黑人和棕色人前伯尼·桑德斯的支持者在特朗普的火车上!!!!特朗普永远!!!!!"图片作者。
这类活动还有很多例子。机器人似乎有各种各样的策略。他们中的一些人只是在一个视频上重复发布相同的评论。有的在各种视频上发布各种评论。据我所知,每个机器人都保持一致的支持特朗普或反对特朗普的立场,但在其他方面似乎遵循非常相似的策略。我查阅了几个机器人的档案,没有一个一致的模式。有些只有很少的相关信息,并且是在过去几个月内创建的。有些人喜欢视频和播放列表,似乎曾经属于真人。
结论
根据我们的分析,我们注意到政治分歧的双方都有许多煽动性的言论。我们可以使用数据科学和计算语言学来寻找这种话语中的模式。使用该数据集,进一步的分析可以探索以下一些内容:
- 什么特征可以预测一条评论会获得多少赞?
- 我们能不能长期跟踪一个 YouTube 用户,看看我们能否根据他们的评论来衡量任何激进行为?
- 我们能否训练一个文本分类器(或许使用神经网络),能够准确区分支持特朗普和反对特朗普的情绪?
另一方面,CNN、福克斯新闻频道和 MSNBC 等主流新闻媒体似乎对巨魔机器人存在严重问题。许多账户在他们的视频上发布高度煽动性的评论,偶尔每分钟多次。有趣的是,政治光谱的两边都有机器人——支持和反对唐纳德·特朗普。根据一些理论,这可能是试图削弱人们对美国民主和即将到来的选举的合法性的信心。
我希望这篇教程能让你对如何在 Python 中使用 API、如何用 Pandas 组织数据库、如何用 Plotly 可视化以及如何在 Python 中使用单词嵌入有所了解。我鼓励你们收集自己的数据,并以代码为起点进行自己的调查。分析 YouTube 上的政治话语还有很多工作要做,更不用说整个社交媒体了。我很乐意听到您的反馈以及您的任何发现!
参考
[1] Gensim word2vec,【https://radimrehurek.com/gensim/models/word2vec.html】T2
[2] "sklearn.manifold.TSNE ",Sci-kit Learn 文档,https://Sci kit-Learn . org/stable/modules/generated/sk Learn . manifold . tsne . html
[3] YouTube 数据 API,https://developers.google.com/youtube/v3/docs/
[4]“普京正在偷下一次选举的路上”,The Atlantic,https://www . theatlantic . com/magazine/archive/2020/06/Putin-American-democracy/610570/。
[5]“t-SNE”,劳伦斯·范德马腾,https://lvdmaaten.github.io/tsne/
[6]“谷歌新闻和列夫·托尔斯泰:使用 t-SNE 可视化 Word2Vec 单词嵌入”,谢尔盖·斯梅塔宁,https://towards data science . com/Google-News-and-Leo-Tolstoy-Visualizing-Word 2 vec-Word-embedding-with-t-SNE-11558 D8 BD 4d
[7] "TfidfVectorizer ",scikit-learn,https://scikit-learn . org/stable/modules/generated/sk learn . feature _ extraction . text . TfidfVectorizer . html
基于 Python 的 WhatsApp 聊天分析器
以有趣的图表形式分析你的 WhatsApp 聊天
几周前,我和妻子开始查看我们去年在 Whatsapp 上的聊天记录。消息太多了,我们简直无法一一查看。我使用 Python 已经 3 年多了,我想创建一个非常简单的分析器,为我提供一些关于我们 WhatsApp 聊天的有用见解。生成的图表非常有趣。
在本文中,我将介绍我为分析 WhatsApp 聊天而创建的 GitHub 存储库。
分析在线演示:
要查看易于使用的在线演示,而不经历安装存储库和 Python 包的麻烦,请单击下面并按照说明进行操作。
什么是 AnalyzeTheChat?
AnalyzeTheChat 是一个基于 Python 的存储库,为你的 WhatsApp 聊天生成有用的统计数据。它会抓取提供的。txt 文件,并生成一个熊猫数据帧。然后对该数据帧进行处理,以绘制以下接触式和时间式统计数据
联系方式统计:
- 每个联系人的消息数量
- 每个联系人的字数
- 每个联系人每封邮件的平均字数
- 每个联系人的表情数量
- 每个联系人每条消息的平均表情数量
- 每个联系人的媒体数量
- 每个联系人的关键字数量
时间统计
- 每小时的消息数量
- 每个工作日的邮件数量
- 每月的邮件数量
- 每年的邮件数量
下面是一个生成统计数据的示例
图片:【https://github.com/aqeelanwar/AnalyzeTheChat
如何安装 AnalyzeTheChat?
建议为这个项目创建一个新的虚拟环境(使用 python 3.6)。创建虚拟环境的详细步骤可以在这里找到
1.克隆存储库
创建虚拟环境后,激活它,并使用以下命令克隆存储库。
git clone [https://github.com/aqeelanwar/AnalyzeTheChat.git](https://github.com/aqeelanwar/AnalyzeTheChat.git)
2.安装所需的软件包
cd AnalyzeTheChat
pip install -r requirements.txt
如何运行 AnalyzeTheChat?
1.导出 WhatsApp 聊天
使用以下步骤导出您想要分析的 WhatsApp 聊天
- 打开 WhatsApp 聊天
- 单击右上角的三个垂直点
- 点击更多
- 单击导出聊天
- 单击无媒体
- 保存生成的。可访问的 txt 文件
图片:https://github.com/aqeelanwar/AnalyzeTheChat
2.执行代码
# Generic
python main.py --path <path-to-chat> --save_as <save-type># Example
python main.py --path theoffice.txt --keyword 'jello' --save_as pdf
3.争论
图片:https://github.com/aqeelanwar/AnalyzeTheChat
查看结果
结果以用户通过参数 save_as 选择的格式保存在结果文件夹中。
总结:
AnalyzeTheChat 是一个基于 python 的 WhatsApp 聊天分析器,可以生成有趣的条形图。它可以用来分析一个人或一个群聊的活动,以推断出有用的结果。在线演示可以在这里找到
用 Python 代码分析 A/B 测试结果
照片由mārtiņš·泽姆利克斯在 Unsplash 上拍摄
A/B 测试的七个步骤,以及如何选择能给出 p 值的正确 Python 库。
所以你设计了你的 A/B 测试。现在您运行它,使用您在上一步中确定的样本大小(客户数量)和天数。
现在是关键时刻了。销售团队和网页设计团队都在急切地等待你的回答:你的公司为网站引入了一个新功能;这个特性是像销售团队预测的那样降低了销售额,还是像网页设计团队预测的那样保持了销售额不变?
基本上是销量减少了还是没有?
他们还可以问“网站上的新功能是减少了销售额还是增加了销售额?”。这是有区别的——第一个询问是否有变化,第二个询问变化是积极的还是消极的。我们稍后将讨论第二个问题。
我们在之前的帖子中讨论过 p 值。在那篇文章中,我们还研究了68–95–99.7 规则。p 值和 68–95–99.7 规则以一定的置信度回答了上述问题。
运行 A/B 测试的步骤
- 确定您将运行测试的天数
- 确定样本量——每天有多少客户
- 决定你的无效假设
- 决定你想要的自信程度
- 对 1 完全运行测试。第二。超过
- 获取 p 值
- 对零假设作出断言:要么拒绝,要么拒绝失败
问题设置示例
让我们来看看与前几篇文章相同的例子。我们的客户群平均购买价值 170 美元的产品。网站设计团队想要引入一项新功能,但是销售团队对此感到担忧。您运行 A/B 测试,看看新特性是否会改变平均订单值。你将有一个对照组,他们将看到旧的网站,还有一个目标组,他们将看到有新功能的新网站。
1.确定您将运行测试的天数
我们在之前的帖子中讨论过这个问题。让我们假设我们将运行测试 14 天。
2.确定样本量——每天有多少客户
我们在之前的帖子中也讨论过这个问题。假设我们的样本中有 10,000 名客户。
3.决定你的无效假设
在这种情况下,零假设是新特征不会改变平均阶数值。也就是说,对照组和目标组的平均订单价值都是 170 美元。换句话说,控制组和目标组将从相同的人群中抽取,就好像他们看的是同一个网站。这是我们将要检验的假设。
4.决定置信水平
通常,企业会告诉你他们对结果的信心水平。一般来说,大多数企业采用 95%的置信区间。也就是说,你要 95%确定控制组和目标组来自同一人群;也就是说,如果控制组和目标组在 5%的时间里看起来不像来自同一个群体,这是可以的。所以我们的临界 p 值将是 0.05。
5.运行测试
现在坐下来,对 1 中确定的参数进行测试。第二。没有峰值。如果你需要达到峰值,我们需要设计一个不同的 A/B 测试,比如 Bandit 测试。A/B 测试首先探索,然后在测试结束后利用。一个强盗同时探索和利用,或者学习和赚钱。
我们正在进行 A/B 测试,所以让我们探索整整 14 天。每天我们向对照组展示旧网站,在测试结束时,我们会有 14 个平均订单值。在这 14 天中的每一天,目标群体中的客户都会看到新网站,在测试结束时,我们还会看到他们的 14 个平均订单值。
像这样:
control means = cm = [169.84, 166.66, 170.5, 168.09, 168.36, 171.28, 169.53, 169.25, 171.66, 171.03, 168.89, 171.04, 171.20, 170.22]target means = tm = [174.54, 171.95, 177.06, 174.33, 174.85, 174.34, 176.89, 176.71, 172.81, 176.22, 177.45, 177.10, 175.23, 177.09]
6.获取 p 值
有许多方法可以从两个样本中获得 p 值。
在这种情况下,我们将比较两个样本,看它们是否相似。Kolmogorov-Smirnov 测试是最简单的方法之一,也在 Python 中实现。
import scipy.stats as stats
stats.ks_2samp(cm, tm)
给你:
Ks_2sampResult(statistic=1.0, pvalue=4.985467362637908e-08)
这个 p 值是什么意思?这意味着,给定上面给出的对照样本平均分布(cm 的数组),我们获得上面的目标样本平均分布(由数组 tm 给出)的概率是 5.0x10-⁶ %(来自上面的 pvalue 4.985467362637908 e-08 乘以 100)。也就是说,目标样本来自与对照样本相同的群体的概率非常非常低,约为十亿分之五十。
7.对零假设作出断言:要么拒绝,要么拒绝失败
5.0x10-⁶ %的 p 值比我们在上面第 4 步中设定的容差 5%要小得多。因此,看来我们必须拒绝目标样本和控制样本来自同一人群的假设。
让我们实际绘制上面 cm 和 tm 给出的控制和目标均值,看看它看起来像什么:
如果我们用蓝色绘制控制平均值(上面的 cm 阵列),用橙色绘制目标平均值(上面的 tm 阵列),我们会发现它们确实不同,与顶部的图不同。控制均值似乎以 170 美元为中心,而目标均值似乎以 175 美元为中心。如果您查看每对数组(对照减去目标)之间的差异,您会得到底部图,该图显示每天目标平均值都高于对照平均值(x 轴始终为负)。
所以目标均值始终不同于控制均值。这就是我们的 p 值所暗示的。
注意,目标平均值实际上一直高于控制平均值,但这不是我们在步骤 3 中试图发现的;我们试图发现对照样本和目标样本是相同还是不同,而不是更高或更低。所以我们无法对此做出具体的结论。如果这是一个真正的问题,我们将不得不运行另一个 A/B 测试来找出答案。比赛开始后不要移动球门柱!
示例 2
让我们看第二个例子:
cm = [169.84, 166.66, 170.5, 168.09, 168.36, 171.28, 169.53, 169.25, 171.66, 171.03, 168.89, 171.04, 171.20, 170.22]
tm = [169.66, 167.14, 172.11, 169.44, 169.95, 169.46, 171.94, 171.77, 167.97, 171.29, 172.49, 172.14, 170.33, 172.13]
stats.ks_2samp(cm, tm)
Ks_2sampResult(statistic=0.42857142857142855, pvalue=0.15493471530488623)
在这种情况下,由于 0.15 的 KS 检验 p 值大于 0.05 的临界 p 值,我们说我们未能拒绝零假设;我们不能否认对照样本和目标样本来自同一分布。
鉴于‘拒绝失败’是一个双重否定,难道它不意味着我们接受零假设吗?不,不是的。我们永远不能接受零假设,或者说两个样本来自相同的分布,有 100%的把握。为什么?因为我们一开始就给自己定了一个 95%自信的目标。再次声明,比赛开始后,不要移动球门柱。
你可能会问,我们能不能不把信心水平定在 100%呢?如果我们这样做,我们的尾部容差将是 0.0(从 100%–100%;之前,当我们要求 95%的置信度时,我们的容差是 5%)。也就是说,我们的临界 p 值是 0.0,无论我们做什么,实验的 p 值总是高于 0.0,我们必须始终拒绝零假设。
使用哪种算法来比较两个样本
Python 有许多用于计算 p 值的库。您可以使用它们中的任何一个,语法同上。
我们应该使用哪一个?这里有一个宽松的指导方针:
双样本 t-检验(学生 t-检验)如果您确信控制样本和目标样本均值呈正态分布
或
如果您测试超过 30 天(因为超过 30 天中央极限定理开始生效并保证常态— 30 不是一个神奇的数字,但在行业中被广泛接受。)
Kolmogorov-Smirnov 检验(KS 检验)你无法保证对照和目标均值呈正态分布
或者
你的测试中没有多少天(工业上公认 30 天左右就足够了,虽然这个数字背后并没有什么魔力。在任何情况下,没有企业会允许你运行 30 天的 A/B 测试。)
或
你认为两个样本的均值、方差和分布形状是一样的。
或
如果你想捕捉一天中控制样本和目标样本最大的不同,使用这个测试。KS 测试对测试中最大的差异很敏感。
Mann-Whitney 秩检验你无法保证控制和目标均值呈正态分布
或者
你的测试中没有多少天(工业上公认 30 天左右就足够了,虽然这个数字背后并没有什么魔力。在任何情况下,没有企业会允许你运行 30 天的 A/B 测试。)
或
你认为两个样本的均值、方差或分布形状不同。
如果您不确定使用哪一个,一个好的经验法则是使用 Mann-Whitney 秩检验。
这个故事的寓意
- 如果测试 p 值< critical p-value reject the null hypothesis
- if test p-value >临界 p 值未能拒绝零假设
两个重要的教训
- 假定零假设为真,p 值给出了我们观察到的现象的概率。它没有告诉我们零假设为真的概率。
所以在我们的第一个例子中,零假设是目标样本和对照样本都来自同一人群。那么我们可以说,假设目标和对照样本均值 tm 和 cm 都来自同一个总体,那么观察到它们的概率是 5.0x10-⁶ %。十亿分之五十的机会,非常小的概率。所以我们拒绝零假设:我们拒绝两个样本来自同一个分布的假设。
它没有告诉我们零假设本身为真的概率。
开车回家是一个很难的教训。但总的来说,在生活中,我们无法说出任何假设为真的概率——我们所能说的只是根据某人的假设进行观察的概率。
2.有了 p 值,我们要么拒绝零假设,要么无法拒绝零假设。你永远不能接受无效假设。
没有拒绝原假设不等于接受原假设。这是因为当我们设定置信水平时,我们总是将自己设定在 100%以下,这意味着我们永远不能 100%确定零假设是真的。
分析 NHL 曲棍球的十年
2010 至 2019 赛季
塞斯·赫夫曼在 Unsplash 上拍摄的照片
T4 我第一次穿溜冰鞋的时候,只有 6 岁。我的弟弟才 4 岁。9 岁时,我开始接受花样滑冰训练。一天,当冰球队到达时,我正试图做一个非常简单的跳跃,我正努力跟随溜冰场右边的圆圈。我不知道该怎么解释。他们停下来时溜冰鞋在溜冰场上的声音让我大吃一惊。车轮撞击瓷砖的声音。传球时球撞击球棍的微妙声音。那天我被卖了。训练结束后,我和我的教练谈了谈,当天我就换成了曲棍球。我整个业余生涯效力的那个俱乐部的主席给了我她儿子的球棍,是给初级球员用的小球棍。我这辈子再也没想过滑冰了。从那天起,我成了一名冰球运动员,并自己发出了那美妙的声音。这不是冰球。这是直排轮曲棍球,四个轮子平行。
这项工作的动机
今天我不再打冰球了,也没有机会打冰球了。在这项工作中,我结合了我的两个爱好:数据科学和曲棍球。
这项工作的主要动机是开始寻找一些关于过去十年曲棍球成绩的问题的答案。我将根据我在 Kaggle 中找到的数据回答以下问题。
- 联盟中最好的守门员有哪些?
- 联盟最佳得分手球员有哪些?
- 进球多的球队有哪些?
- 每支球队中罚分最多的球员是谁?
- 生产 NHL 球员最多的国家是哪个?
让我们开始分析。在我的 GitHub 存储库上可以找到关于几个 CSV 文件的细节和数据清理与合并的笔记本。
*“出产更多 NHL 玩家的国家”*图来源此处。该图被上传到 Tableau public 以便于访问。
联盟中最好的守门员有哪些?
我从守门员开始,因为当我在工作中接触到他们时,我发现了一个非常重要的关于扑救百分比的问题。如果我们根据扑救百分比得分按降序绘制前 10 名守门员的图表,我们会得到以下图表:
这里发生了什么?他们中没有一个是我们在每场比赛中经常看到的守门员,他们都有 100%的平均扑救率!答案是这些大多是“紧急守门员”。你还记得黑鹰队著名的签约紧急守门员斯科特·福斯特 吗?他在该队任职期间从未参加过正式比赛。直到 2017-2018 赛季的那个幸运的夜晚,所有黑鹰队的守门员都受伤了,他被叫出来(终于)穿上衣服。他打了“他一生中最长的 14 分钟曲棍球”,正如他在随后的无数次采访中描述的那样。他打了大约 14 分钟,没有进球,所以他的扑救率是 100%(在他的整个 NHL 职业生涯中!).为了解决这个问题,我考虑了两种选择:
以 100%的扑救率消灭所有守门员
这种选择对于经验丰富的守门员和球员来说是不公平的。经验丰富的守门员球员在已经进了 3-4 个球(或更多)时进入冰场代替首发守门员。所以在这种情况下,他们应该得到 100%的储蓄。
确定分析中需要考虑的最少季节数
这种方法也有一些警告,因为这产生了一个问题,即什么使最少季节数正确?尽管有这种担心,我还是选择了这个选项。我决定设定最少 5 个赛季,每个赛季 81 场比赛(41 个主场冰加 41 个客场)。
前 10 名守门员平均。保存百分比
现在我们看到了常见的嫌疑人。图克卡·拉斯克成为平均得分更高的守门员。拯救百分比其次是我的两个最爱:凯里·普莱斯和亨里克·伦德奎斯特。正如你在这里看到的,他们的数字非常接近。它被认为是一个 NHL 守门员!期望值非常高。
联盟最佳得分手球员有哪些?
让我们从总进球数的角度来看最好的球员。
这并没有真正告诉我们事情的全部。这不是 100%准确或公平的,因为有些球员比其他人打得更少,但每场比赛和/或每场冰上时间(每场比赛的时间)得分更多。让我们看看同样的指标,但现在考虑到玩的游戏。
前 10 名球员每场比赛的进球
每场比赛的进球数告诉我们亚历克斯·奥韦奇金保持着第一的位置,因为他打了很多比赛,也进了很多球!他几乎每隔一场比赛就进一个球。史蒂文·斯塔姆科斯以非常相似的数字紧随其后,但比奥韦奇金少打了 100 多场比赛。希尼·克罗斯比和叶夫根尼·马尔金已经跃居榜首,使得企鹅队成为一支非常危险的队伍!毫不奇怪,几乎所有人都是各自球队的队长和前锋/边锋。来自达拉斯明星队的泰勒·塞金(Tyler Seguin)出场次数明显更少,仅为 489 场,但占据第 5 位,因此他的进球非常有效。另一方面,帕特里克·凯恩在参加的比赛中排名第二,但进球很少,排名倒数第二。
让我们来看看那些花更多时间在冰上的球员,看看这是否与进球数有关。
十大防御平均值。冰上时间
按冰上时间排序(TOI)前 10 名都是防守队员。平均每场防守时间大约是 14-25 分钟。那几乎是游戏的一半!瑞安·萨特虽然没有出现在很多比赛中,但还是打了很多分钟。国王队的德鲁·多尔蒂在这十年中几乎一直打半边天比赛。
过滤掉防守球员,我们看到前锋平均 TOI。
前 10 名球员(无防守)平均。冰上时间
前锋在冰上的时间更少,因为他们倾向于滑得更多。安泽·科普塔尔、瑞恩·盖茨拉夫和希尼·克罗斯比位居榜首。亚历克斯·奥韦奇金并不在这张图表的首位,但他是一名非常有效率的球员,因为他每场比赛都有进球。
进球多的球队有哪些?
我计算了每队进球最多的 3 名球员的平均值,以此来透视每队的得分能力。下图显示了由于三名得分最高的球员,该队得分能力更强。
每支球队前 3 名得分手的总进球平均值
这里我们可以看到鲨鱼队前三名球员在过去十年中比其他球队进了更多的球。紧随其后的是最危险的队伍黑鹰和企鹅分别位居第二和第三。首都,然而,尽管亚历克斯·奥韦奇金的巨大的生产,只是在第四位。
令人惊讶的是,当看球队每场比赛的进球数时,星是每场比赛进更多球的球队(保证每 3 场比赛进 1 球!).紧随其后的是闪电、 黑豹和油工。企鹅队排在第五位,离每三场比赛进一球还差一点。
球队每场比赛的平均进球
每支球队中罚分最多的球员是谁?
令人惊讶的是这些都不是防御。他们是前锋。参议员克里斯·尼尔每 6 场比赛 10 次坐在罚球线上,高居榜首!。紧随其后的是科迪·麦克劳德和汤姆·威尔逊,他们每场比赛都坐在罚球点上。他们都以力量和身体著称。
前 10 名球员每场比赛的罚球时间
生产 NHL 球员最多的国家是哪个?
发现加拿大为国家冰球联盟提供的球员比其他任何国家都多,这并不奇怪。
从 2010 年到 2019 年生产更多 NHL 球员的国家
在过去的十年里,加拿大球员的总数达到了 1149 人。其次是美国带 651 然后是瑞典带 184 。加拿大玩家的数量几乎是美国玩家的两倍,而美国玩家的数量是瑞典玩家的四倍。
其余前 10 名的国家总共只有 380 名球员。
结论
这只是对 NHL 曲棍球十年的描述性分析。在这项工作期间,出现了很多问题,为了能够按时完成,我在这篇文章中忽略了这些问题。我计划在这篇文章的第二部分继续回答其他有趣的问题。例如,未来帖子中会出现的内容有:
- 球队如何将罚球时间与球队输掉比赛联系起来?
- 这和球队在主场还是客场比赛有关系吗?
- 在比赛的最后一节(第三节)或加时赛(加时赛)中,得分最高的球员有什么不同吗?
你最喜欢的球员出现在这部作品的排名中了吗?请在评论中告诉我。此外,让我知道你还有什么兴趣看到第二部分的分析。感谢您的阅读!
使用 Python 包分析 Babar Azam 的 ODI 统计数据-基础版
图片提供板球世界杯
要在巴基斯坦板球运动中留下印记,让你的名字出现在历史书上,你通常需要的不仅仅是几场精彩的场上表演。在这个 T20 疯狂的时代,你需要在你的游戏中充满活力,并拥有一种全力以赴的光环。你需要在媒体上喋喋不休,每隔一个月尝试不可思议的发型,放大你的运动性感度,以你的名字为名大赚一笔。
巴巴尔·阿扎姆(Babar Azam)在 2015 年出现,这个年轻、身材普通、看起来不那么大男子主义的家伙,很少有人想到他最终会重新定义和振兴巴基斯坦对蝙蝠人的看法。像巴基斯坦这样的国家,其体育历史上充满了快速保龄球现象,很少有机会珍惜这样的奇迹男孩的存在。
随着时间的推移,巴巴尔在各种形式的游戏中的表现越来越好。在他相当年轻的职业生涯中,这位 25 岁的球员已经提升到新的高度,在这个过程中打破了几项著名的击球记录。他赢得了被称为限量版一致性的新毕加索的权利,尤其是现代 ODI 板球比赛中的击球巨人。
在本文中,我使用 Python 包和工具对 Babar 的 50-over 格式的数字进行了基本的处理和分析。然而,就像这项运动本身一样,总是有增加赌注的空间,因此,更先进的数据分析技术肯定是适用的,值得一试。关于这一点的更深入的文章将紧随其后。
我首先导入所有必要的 Python 库,我将在本文中以这样或那样的方式使用它们。
然后,我使用 Cricpy 包中的函数‘getPlayerDataOD’从 ESPNCricinfo 的数据库中获取 Babar Azam 的 ODI 配置文件。传递函数参数的特定参数以提取所需的数据帧,该数据帧存储在名为“babar.csv”的. csv 文件中。我用的是熊猫的。read_csv 方法来读取和解释获取的数据。
这是我们手头的数据框架的一个快速预览。
标题为“HA”的最后一列不在从 ESPNCricinfo 楔入的原始数据帧中。我不得不使用一些基本的 MS Excel 技术手动添加它,以分析与主场和客场比赛相关的数据,我将在稍后深入研究这些数据。数据框总共有 74 行(这个片段只显示了前 20 行),指的是 Babar Azam 参与的所有 ODI 匹配。
在此之后,我执行了一些数据清理任务,其中包括清理属性名称以方便索引,检测并删除不必要的括号、逗号和破折号,去除对立列中的“相对”符号,并从击球手没有击球的数据框中删除那些匹配,即我们在跑垒列中有 DNB。我还必须考虑不同属性的错误数据类型,并使用 Pandas 函数改变它们。
Total ODI 与所有反对意见背道而驰
有了一个完善的数据框架,我现在开始探索性数据分析部分。首先,我绘制了巴巴尔在棒线图上所有对手的累计点数。
如上图所示,巴巴尔在与英国人的比赛中取得了突飞猛进的成绩。他参加了 16 场对阵 Poms 的比赛,其中 12 场是在英格兰举办的。在 2019 年对阵英格兰的六场 ODI 中,巴巴尔积累了过多的得分——340 分,平均得分为 56.67 分——包括在特伦特大桥的精彩世纪。如果这本身还不令人敬畏的话,那么他对抗温蒂家族的记录是超凡脱俗的。在巴巴尔面对他们的七场比赛中,他以惊人的平均低于 90 杆的成绩拿下了四吨。
第四次 ODI 的精彩部分,Babar 在那里得分一吨
对抗所有对手的平均打击率
在游戏的现代时代,击球率已经成为衡量板球运动员的击球能力和他对游戏的影响的重要指标,无论是在历史还是现在的背景下。巴巴尔是为数不多的在 T20Is 和 ODIs 中平均年龄超过 50 岁的著名板球运动员之一。只有少数其他击球手享受这样的壮举。
我创建了一个名为 avgOppFinder 的函数,它返回 Babar 相对于指定对手的平均值。我将该函数应用于对立列的各个组,即各个对立,并将结果序列对象转换为 Pandas 数据框。我还必须考虑到 Babar 没有出局的那几局,在清理数据时,我在 Runs 列中使用星号(*)符号来处理它。生成的数据框片段粘贴在下面:
我使用了一个散点图来显示巴巴尔的个人平均值,以递增的顺序,如气泡的大小所示。除了印度之外,巴巴尔一直保持着 40+的平均打击率,对抗所有八大 ODI 国家。鉴于他在比赛形式和标准方面的成就,他超越印度和新西兰的记录也只是时间问题。
巴巴尔·阿扎姆划定的边界的视觉表现
巴巴一直以优雅和简单的化身而闻名,并以最被低估的方式完成他的跑步。他累积分数的轻松程度是无与伦比的。他讲的不是牛角尖上的丑陋,他讲的是华丽的车道。他不是关于野外跋涉和滑雪者,他是关于交货和创造差距的巧妙机动。他不是那种把厨房的水槽扔向一切事物的人,相反,他是不祥的平静和冷静。
我试图通过描绘巴巴尔在 ODI 板球赛中所有局的界限来想象他平静的方式。上面的散点图描绘了他得到的 4 分与他达到的最高分之间的巨大反差。在他的整个职业生涯中,到目前为止,巴巴尔一局只有两次打出四个六分。事实上,大多数时候,他投了 0 或 1/6 局,但他仍然以他所拥有的好球率平均得分超过 50。巴巴尔·阿扎姆与众不同。
所有场馆的 ODI 总量
接下来,我使用另一种分组策略来根据地点总结巴巴尔的跑步记录。这里值得一提的是,Babar 在 2015 年津巴布韦对该国的历史性访问期间首次亮相巴基斯坦。尽管如此,这位右撇子还是在巴基斯坦的第二故乡阿拉伯联合酋长国打了很多比赛。正如剧情所示,他在阿联酋的沙丘上多次得分,就得分而言,阿布扎比是他得分最多的地方。巴巴尔对英国场地的热爱,尤其是对洛德桥和特伦特桥的热爱,在这些酒吧中也非常突出。然而,他在 2019 年世界杯上对阵新西兰的精彩比赛并没有在这两个场馆举行。那是在 Edgbaston(伯明翰),许多人认为这将是一个传奇人物职业生涯的转折点。
巴巴尔的职业生涯定义了 2019 年在 CWC 举行的 ton v New Zealand
每天运行的 ODI 总数
在玩巴巴尔的数字时,我偶然发现了一个有趣的统计数据,那就是他在不同的日子里积累的跑步记录的多样性。他在周二打了 7 场比赛,周日打了 17 场,周日他又跑了将近 926 次。周二,巴巴尔的最高分只有区区 22 分,还有几个个位数的分数。然而,在周日的 ODI 比赛中,巴巴尔变成了击球巨兽。在 17 场比赛中,他有四个世纪和五个半世纪的成绩,总成绩超过 1000 分。可以肯定的是,在周日的晚上,他的脚步有了额外的弹性,但是我们永远不会知道这个秘密背后的原因。下图显示了惊人的差异。
随着花费的分钟数和运行次数的变化而改变命中率
早些时候,我们讨论了 Babar 如何在折痕处保持冷静,并依靠计算的风险而不是艰苦奋斗和夹击来以更快的速度得分。尽管没有吹嘘自己的身材和体格,也没有以击球出界而闻名,巴巴尔还是设法保持了略高于 87 的职业生涯击球率,这与现代标准大致相当。
在这里,我试图描述他的命中率如何飙升到 100 分以上,以及当他花更多的时间在折痕上和面对更多的传球时,他的跑位得分能力如何进一步提高。下面,我绘制了两个散点图,它们有一个共同的 x 轴的打击率。
累积罢工率
为了进一步证明 Babar 的罢工率的一致性,我使用 Cricpy 的 batsmanCumulativeStrikeRate 函数绘制了他在整个 ODI 职业生涯中的累计罢工率。线图显示了他在职业生涯中期的击球率略有下降,但图表的后半部分显示了他在最近三场比赛中的击球率为 97.95,109.52 和 119.23,朝着顶峰不断回升。
巴巴尔的解雇
现在是时候深入研究 Babar 在 50 岁以上格式中的解雇模式了。我决定用一个饼状图来描述这一点,这个饼状图显示了到目前为止他被解雇的比例。不出所料,他已经被抓了无数次,几乎占他总解雇数的五分之三。
作为一个狂热的板球观察员,我确实意识到他出局的比例仍然偏高,但最终会下降。在他职业生涯的早期,Babar 在面对快速点击投球的投球手时,有一种发现自己的脚卡在折痕处的松散倾向。这使得他容易受到马可·伍德、乔希·黑兹伍德等保龄球手的冷不防的夹击。巴巴尔在技术上做得非常出色,以稳定他的平衡和保持稳定的形状。现在他是板球教科书认可的成品。
饼状图的另一个发现是巴巴尔对抗旋转保龄球的明显实力。在他的整个职业生涯中,他只被难住了一次,这显示了他在击球时的纪律性。这并不是说巴巴尔不用他的脚来对付旋转器,而是几乎一直呆在折痕里。通常情况下,当他不得不强行解决问题时,你会看到他在跑道上跳舞。这是他从投球手手中获得变化并相应地操纵或击打球的精确度。很少看到他被一个旋转手迷惑,然后扔掉他的三柱门。
跑步—国内和海外
巴巴尔的主客场表现有很大的反差,就像大多数打了这么多比赛的击球手一样。我尝试使用 Cricpy 中的 batsmanPerfHomeAway 函数对此进行分析,下面粘贴了一段摘录。它将击球手在国内和海外场地的得分绘制在盒须图上,以说明并排比较。如图所示,主场得分的四分位数范围比客场得分的四分位数范围大得多。得分的中位数在主场也相对较高。
预测巴巴尔的跑步
接下来,我试着制定一个基本水平的预测器,根据指定的球数和在折痕上花费的时间来预测巴巴尔的得分。我使用了 Cricpy 库中的 batsmanRunsPredict 函数,该函数拟合了击球累计次数和花费时间之间的线性回归平面。为了检索正确的数据,我不得不忽略 BF 和 Mins 列中有破折号(-)的那些比赛,这是指该信息对于特定游戏不可用的事实。我将输出的 Series 对象转换成数据帧并打印出来,如下所示:
我还利用 batsmanScoringRateODTT 函数的帮助,根据 Babar 面临的递送,在他进行的运行之间使用二阶多项式来进一步计算和绘制 Babar 的预测得分率。
巴巴尔与 Fab Four 的比较
很长一段时间以来,国际板球界谈论的话题是巴巴尔如何融入享有盛誉的 Fab Four 集团?他是第五个吗?他如何与优雅、高贵的乔·鲁特、稳重、镇定的凯恩·威廉姆森、潇洒、大胆的维拉特·柯利以及异端、无情的史蒂夫·史密斯正面交锋?他真的有能力吸引现代击球巨人的眼球吗?
为了进行这些比较,我必须首先从 ESPNCricinfo 中检索所有这四名球员的数据帧,方法与我在本文开始时使用的方法非常相似。然后,我使用名为 relativeBatsmanCumulativeAvgRuns 的函数的一部分来计算所有五个玩家的累积平均值,如上面的 for 循环所示。compRuns 函数是我自己定义的函数,用于执行所有这些计算并绘制结果,如下所示:
正如这个线图所示,巴巴尔的累积平均得分遥遥领先于威廉姆森和史密斯等人。他甚至在最初的 70 局 ODI 比赛中击败了庞大的 Virat Kohli。当然,大约就在这个时候,Kohli 的地位加速上升到了前所未有的高度。然而,这并不意味着我们可以忽视巴巴尔在其职业生涯早期所记录的怪异数据。
另一个证明巴巴尔符合精英击球手群体设定的标准的标准是巴巴尔在他的前 70 场比赛中的得分数,与所有四名击球手在各自的前 70 场比赛中的得分数相比。巴巴尔又一次以可观的优势击败了所有人。他是 ODI 跑 1000 次的第三名,ODI 跑 2000 次的第二名,ODI 跑 3000 次的第三名。这些数字充分说明了他的能力,也说明了为什么他是当今如此高评价的板球运动员。
巴巴尔·阿扎姆(Babar Azam)与印度次大陆流行、装饰和庆祝的板球品牌格格不入。他不是明星,也不是这个国家的宠儿,但他正尽力脱颖而出,以一种非典型的、非巴基斯坦的方式做出改变。随着他不断累积的得分和不断刷新的记录,巴巴尔很有可能成为巴基斯坦板球史上最伟大的击球手之一。
分析最佳黑客新闻帖子
有史以来最佳黑客新闻帖子的统计和文本分析。
每天我都会查看黑客新闻寻找有趣的信息,无论是文章、故事、软件还是工具。大多数登上头版的投稿都非常有趣和有用,而且社区驱动的帖子管理如此之好的事实让我着迷。
为了这篇文章的目的,我使用了黑客新闻 API 收集了大约 200 篇提交给 Hacker News 的最好的故事和他们的评论,并对数据进行了一些处理,以获得一点关于什么是一篇好的《HN 邮报》的见解。
在我们开始之前,我必须说,我毫不怀疑黑客新闻提交是好的,这要归功于所提供信息的质量和对该特定信息的兴趣程度。但是,可能还有其他因素,在很小的比例上,帮助 HN 提名登上头版。
记住这一点,让我们看看这篇文章的概述:
- 为我们的分析获取数据
- 数据可视化:单词云和分数分析
- 何时在 HackerNews 上发帖
- 问 HN vs 秀 HN
- 黑客新闻上的人们谈论谁:实体识别和关键词提取
- 结论
本文原载于 程序员背包博客 。如果你想阅读更多这类的故事,一定要访问这个博客。
对更多这样的故事感兴趣?在 Twitter 上关注我,地址是@ b _ dmarius,我会在那里发布每一篇新文章。
获取数据进行分析
我使用了 HackerNews API /beststories 端点收集了 188 个有史以来最好的故事。对于每个故事,我也收集了评论(但不是对评论的评论,只有主线)。这是我为每个条目存储的数据。
id - the id of the entry
parent - the id of the parent. For a story, it is the same as the id field. For a comment, it's the id of the story to which the commend was added
kids_number - only for stories, meaning the number of comments
score - only for stories: the number of points the submission got
time - UNIX timestamp of the time the entry was added
text - title of posts or texts of comments
type - 'story' or 'comment'
我用来获取数据的类的完整代码将在本文末尾提供。
然后,数据存储在 csv_file 中,并从那里加载到 Pandas 帧中。我还需要为我的分析创建另外 4 列: DayOfWeek,HourOfDay,isAsk,isShow。这些名字不言自明。
dataFetcher = DataFetcher("https://hacker-news.firebaseio.com/v0/", "data.csv")
dataFetcher.fetchData() df = pd.read_csv("data.csv") df['DateTime'] = pd.to_datetime(df['time'], unit='s')
df['DayOfWeek'] = df['DateTime'].dt.day_name()
df['HourOfDay'] = df['DateTime'].dt.hour
df['isAsk'] = df.apply(lambda x: x.type=='story' and x.text.lower().startswith("ask hn:"), axis=1)
df['isShow'] = df.apply(lambda x: x.type == 'story' and x.text.lower().startswith("show hn:"), axis=1)
数据可视化:单词云和分数分析
我首先对数据做了一些探索性的分析。首先,我从故事标题和评论中建立了两个独立的单词云,希望我能对 HackerNews 上常用的单词有所了解。我已经从标题中删除了“展示 HN”和“询问 HN”的标签。
stopwords = set(STOPWORDS)
stopwords.update(["Ask", "Show", "HN"])
titles_text = " ".join(df[df['type']=='story']['text'].unique())
titles_cloud = WordCloud(stopwords=stopwords, background_color='white').generate(titles_text)
plt.figure(figsize=(8, 8), facecolor=None)
plt.imshow(titles_cloud, interpolation="bilinear")
plt.axis("off")
plt.tight_layout(pad=0)
plt.show()
从故事标题构建单词云
除了大的、明显的 Covid 和冠状病毒词,大多数词都与软件、编程和技术有关。一个很好的观察是,视频似乎在黑客新闻上工作得很好(至少这个词云告诉我们)。
也来看看评论吧。
comments = " ".join(df[df['type'] == 'comment']['text'].unique())
comments_cloud = WordCloud(background_color='white').generate(comments)
plt.figure(figsize=(8, 8), facecolor=None)
plt.imshow(comments_cloud, interpolation="bilinear")
plt.axis("off")
plt.tight_layout(pad=0)
plt.show()
从评论中构建单词云
我有一点失望,因为我没有包括所有关于这个分析的评论,但是评论的数量非常大,我不确定它对我的这篇文章有多大帮助。但是我们都知道有时候我们花在评论区的时间比花在最初提交的帖子上的时间还多😀
然后我想看看最好的帖子的分数。我绘制了一个直方图来说明分数倾向于聚集的值,我还计算了分数的平均值和中值。
# Histogram of scores scores = df[df['type']=='story']['score']
scores.plot.hist(bins=12, alpha=0.5)
plt.show() # Average score
print ("Average score: ", df[df['type']=='story']['score'].mean()) # Median score
print("Median score: ", df[df['type'] == 'story']['score'].median())
黑客新闻最佳帖子得分直方图
我们可以看到,大多数故事的得分都低于 200 分,但也有一些异常值,至少有 1000 分。
我的数据集的平均得分为 194.80,但是这受到了异常值的巨大影响。这就是为什么我还计算了的中间值,它是 140.0。也就是说,黑客新闻上大约一半的最佳报道得分不到 140 分,而另一半得分超过了 140 分。
何时在黑客新闻上发布
这是很多人在网上问的问题。这篇文章绝不是寻找答案的捷径,但我仍然认为我找到了一些有趣的东西。
首先,我绘制了一周中每天的故事分布图。
daysOfWeek = df[df['type']=='story'].groupby(['DayOfWeek']).size()
daysOfWeek.plot.bar()
plt.show()
何时在 HackerNews 上发帖——按星期几发帖
大多数最好的故事都是在周末发布的。不知何故,我期待着这一点。但对我来说最有趣的事实是,没有一个最好的故事是在周二或周三提交的。周一似乎也是非常糟糕的一天,很少有成功的提交。
在做这个分析之前,我还会猜测星期五会获得最多的成功提交。我也不知道具体为什么,只是直觉。
我们还可以看看另一个时间维度,那就是一天中的某个时刻。让我们画出同样的分布。
hoursOfDay = df[df['type']=='story'].groupby(['HourOfDay']).size()
hoursOfDay.plot.bar()
plt.show()
何时在 Hackernews 上发帖——按星期几发帖
让我们的时间列以 UTC 时间显示,我们可以看到大多数成功的帖子是在下午提交的,最大的峰值出现在 UTC 时间下午 5 点。
我想检查的另一件事是,一篇帖子获得的点数和该帖子的评论数之间是否有任何关联。对我来说,这似乎很明显应该是真的:如果人们发现一些足够有趣的东西来投票,他们也可能会在那个帖子上开始讨论。
我还在这个关联矩阵中加入了一天中的某个小时,以检查一天中人们是否有更想参与对话的时候。
correlationsData = df[df['type'] =='story'][['score', 'kids_number', 'HourOfDay']]
print (correlationsData.corr(method='pearson'))
何时在黑客新闻上发表文章——相关性
分数和评论数量之间似乎有很强的相关性。正如我所说的,我多少预料到了这一点。但是我对分数和时间之间不存在的相关性有点失望。
问 HN vs 秀 HN
接下来,我想看看黑客新闻上有多少最成功的帖子是提问/展示提交的。
print ("Count of Ask HN stories: ", df[df['isAsk']==True].shape[0])
print ("Percentage of Ask HN stories:", 100 * df[df['isAsk']==True].shape[0] / df[df['type']=='story'].shape[0])
print ("Count of Show HN stories: ", df[df['isShow']==True].shape[0])
print ("Percentage of Show HN stories:", 100 * df[df['isShow']==True].shape[0] / df[df['type']=='story'].shape[0])
似乎只有 8 个帖子问 HN(占我的数据集的 4.30%),16 个帖子显示 HN(占数据集的 8.60%)。毕竟这里没什么可看的,只有几个提交的问题 HN/展示帖子。
黑客新闻上的人们谈论谁:实体识别和关键词提取
下一步是对黑客新闻上的最佳帖子的标题运行一个实体提取器,并从这里保存个人和组织实体,看看是否有任何东西冒出来。我用 spacy 进行实体提取 。
我得到了 175 个实体的名单。因为这是一个没有告诉我们任何事情的大列表,所以我只提取了出现不止一次的实体。
nlp = spacy.load('en_core_web_sm')
doc = nlp(". ".join(df[df['type']=='story']['text'].unique()))
entity_names = [entity.text for entity in doc.ents if entity.label_ in ["PERSON", "ORG"]]
freq = {entity_names.count(entity): entity for entity in entity_names}
for i in sorted (freq.keys()):
if i > 1:
print (freq[i])
# Prints: Amazon, Google, Apple
三家科技巨头是唯一三家在最佳黑客新闻帖子中出现不止一次的实体。
最后一步是 使用 gensim 从帖子的标题中提取关键词 。
print(keywords(". ".join(df[df['type']=='story']['text'].unique())).split('\n'))
这会产生一个巨大的关键字列表,其中前 3 个是:“covid”、“pdf”和“video”。除此之外,大多数关键词都与“生成器”、“应用程序”和“机器学习”有关。
我们不要忘记添加我用来从 Hacker News API 中提取数据的类的代码,正如我在本文开始时承诺的那样。
import csv
import requests
from bs4 import BeautifulSoup BEST_STORIES="beststories.json"class DataFetcher: def __init__(self, baseUrl, dataFile):
self.baseUrl = baseUrl
self.dataFile = dataFile def fetchData(self):
with open(self.dataFile, mode='w') as data_file:
data_writer = csv.writer(data_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
data_writer.writerow(['id', 'parent', 'kids_number', 'score', 'time', 'text', 'type']) # Best stories
r = requests.get(url=self.baseUrl + BEST_STORIES)
bestStoriesIds = r.json()
count = 0
for id in bestStoriesIds:
count = count + 1
print (str(count) + " / " + str(len(bestStoriesIds)))
story = requests.get(url=self.baseUrl + "item/" + str(id) + ".json")
storyJson = story.json()
data_writer.writerow([storyJson['id'], storyJson['parent'] if "parent" in storyJson else storyJson['id'],
len(storyJson['kids']) if 'kids' in storyJson else 0, storyJson['score'],
storyJson['time'], BeautifulSoup(storyJson['title'], features="html.parser").getText(), storyJson['type']]) # Getc
if "kids" in storyJson:
for kidId in storyJson["kids"]:
kid = requests.get(url=self.baseUrl + "item/" + str(kidId) + ".json")
kidJson = kid.json()
if kidJson and kidJson['type'] == 'comment' and "text" in kidJson:
data_writer.writerow(
[kidJson['id'], storyJson['id'],
len(kidJson['kids']) if 'kids' in kidJson else 0, 0,
kidJson['time'], BeautifulSoup(kidJson['text'], features="html.parser").getText(), kidJson['type'], '']) print ("Latest stories")
maxId = requests.get(url=self.baseUrl + "maxitem.json").json()
countDown = 1000
while countDown > 0:
print ("Countdown: ", str(countDown))
story = requests.get(url=self.baseUrl + "item/" + str(maxId) + ".json")
storyJson = story.json()
if storyJson["type"] == "story" and storyJson["score"] > 50:
countDown = countDown - 1
maxId = maxId - 1
data_writer.writerow(
[storyJson['id'], storyJson['parent'] if "parent" in storyJson else storyJson['id'],
len(storyJson['kids']) if 'kids' in storyJson else 0, storyJson['score'],
storyJson['time'], BeautifulSoup(storyJson['title'], features="html.parser").getText(),
storyJson['type'],
storyJson['url'] if "url" in storyJson else '']) # Getc
if "kids" in storyJson:
for kidId in storyJson["kids"]:
kid = requests.get(url=self.baseUrl + "item/" + str(kidId) + ".json")
kidJson = kid.json()
if kidJson['type'] == 'comment' and "text" in kidJson:
data_writer.writerow(
[kidJson['id'], storyJson['id'],
len(kidJson['kids']) if 'kids' in kidJson else 0, 0,
kidJson['time'], BeautifulSoup(kidJson['text'], features="html.parser").getText(),
kidJson['type'], ''])
结论
这就是我对有史以来最佳黑客新闻帖子的小小分析。我真的很喜欢摆弄这些数据。我希望你也喜欢这个,并从这个项目中获得一些有意义的见解。
本文原载于 程序员背包博客 。如果你想阅读更多这类的故事,一定要访问这个博客。
非常感谢您阅读本文!有兴趣了解更多吗?在 Twitter 上关注我,地址是@ b _ dmarius,我会在那里发布每一篇新文章。
分析公民个人的竞选捐款
r 的一项研究。
简介:
竞选捐款在美国政治体系中发挥着重要作用。当大多数人考虑竞选捐款时,他们通常会想到超级政治行动委员会,因为他们向候选人捐款最多。然而,这忽略了来自全国各地的个人贡献。
有了联邦选举委员会(FEC)的公开数据,我们可以从他们的网站观察个人捐款数据。要下载我将使用的数据,请执行以下操作:
- 点击个人捐款
- 下载 2019–2020 年数据。
探索性数据分析:
包含有问题数据的表格并不完美,需要采取一些步骤进行清理,最重要的注意事项是:
- 每个条目由竖线字符
|
分隔 - 列的标签不存在。
- 交易日期列需要重新格式化。
幸运的是,带有数据集的 CSV 文件包含列的标签。对于交易日期,还需要做一些工作。
如果自动读取,R 会假设交易日期是一个数字,因为它就是。日期的结构如下:MMDDYYYY。这个结构很重要。如果月份小于 10,日期的月份将以 0 开始,这意味着 R 决定忽略它,因为它将日期作为整数读入,这使得到 date 对象的转换很困难。
这个问题可以通过加载数据样本、操作交易日期的类,以及使用类集加载更大的数据子集进行探索来缓解,这与具有超过 3400 万个观察值的完整数据集相比,利用了 100 万个样本。在我的笔记本电脑上,这需要大约一分半钟。通过使用更多的行,装载时间会成倍增加。
即使有 100 万个样本,它仍然不能代表更大的图景——特别是考虑到这些数据可以被结构化为时间序列这一事实。通过从 34 个中取出第一个 100 万,我们只观察到捐款的一个小时间窗口。我们将在随后的章节中观察这些故障如何妨碍我们的分析。
时间序列分析:
如前所述,这些数据可以塑造成一个时间序列。然而,在本报告中,分析仅针对前 100 万个样本。在某个特定的日期之后,我们应该期待一个贡献计数的显著下降。有了足够强大的计算机,这个代码可以扩展到分析所有 3400 万次观察。
按作者分列的数字
从上面可以看出,我们的假设是正确的。这意味着前一百万次观察主要发生在 2019 年的前七个月。
状态分析:
一个自然的假设是分解并比较各州的个人贡献。当按每个状态分组并找到它们的相对频率时,需要注意的是有 62 个状态,其中 61 个有效,另一个为空。
但是,怎么会有 61 个有效的状态条目呢?除去我们已经知道的典型的 50 个州和 DC,剩下的 10 个州由联邦/领地、军队州、外国,当然还有空州组成。与 50 个国家和 DC 相比,这 10 个国家的捐款数量微不足道。正因为如此,这次探索的主要焦点将是美国的 50 个州和 DC。
按作者分列的数字
单纯的票数本身就表明了某些州之间的指数差距。然而,单纯的数量并不能代表捐款对选举的影响。事实上,应该预料到加利福尼亚和德克萨斯的投票人数最多,因为它们是最大的州。为了尝试解决这一问题,将使用可视化每个国家的人均捐款数量。
为了找出各州的人均收入,我们将我们的观察值除以各州的人口数。美国人口普查局提供了美国 50 个州的人口数据,这里将使用这些数据。不出所料,包含我们信息的 excel 表格也需要清理一下。关于 excel 文件的重要注意事项如下:
- 由于格式不规则,列名没有被读入。
- 五十个州名前面都包含一个句点(即。德克萨斯”而不是“德克萨斯”。)
- 贡献数据集使用州的缩写(TX ),而人口普查数据使用州的实际名称(德克萨斯州。)
按作者分列的数字
从上面的结果中我们可以看到,纽约和弗吉尼亚的人均捐款数最多,这可能使它们成为筹款的关键州。这进一步强化了这样一种观念,即捐款的数量本身并不能代表整个竞选过程。
谁在大厅里?
因为这个数据集的主要焦点是看个人的贡献,所以只适合分析个人本身。然而,在 100 万个投稿中,几乎有无限多种不同的名字。需要有一种直接的方法来判断谁作为个人对政治有着深厚的热情——甚至更深的口袋。为了找到我们家乡的游说者,我将所有的名字分组,并总结出代表捐款金额的那一栏。从那里,我们可以得到前 20 名贡献者,看看他们愿意付出多少。
然而,这一分析并不完美。我们应该记住,这些是 2019 年上半年最大的贡献者。此外,当查看左边的第三和第四个条时,我们看到这些贡献是由同一个人做出的。然而,在一个例子中,他的头衔(先生)在他的名字里,而在另一个例子中,却没有。基本正则表达式可以通过省略诸如先生、小姐等头衔来清除姓名。然而,这并没有忽视每个人所贡献的大量资金。即使是前 20 名贡献者,托马斯·f·施泰尔(贡献最大)和布鲁士·高富拿(贡献“最小”)之间也有超过 8 倍的显著差距!
结论:
有了一台更强大的计算机,上面进行的分析可以更深入地了解竞选活动在这个国家可以获得的资金种类。尽管超级政治行动委员会凭借其巨大的影响力和影响力主导着政治领域,但仅 2019 年第一阶段的前 20 名捐助者就贡献了超过 1000 万美元的捐款。我们的时间序列是受计算资源缺乏影响最大的分析。这是预料之中的,并在第 3 节中得到证实。正因为如此,假设各州的数据也可能与本报告中的结果不同也是安全的,因为我们无法使用 2019 年上半年来预测另一年的捐款价值。
用 Python 分析芝加哥法院数据
变更数据
从库克县公开法庭数据预测精神健康相关的处置和判决
*Note from the author: This article is based on a project and the report created by myself, Karmen Hutchinson, Kelsey Markey and Alene Rhea for our final project in the course DSGA1001 at NYU CDS. This article reflects my personal learnings, experiences, and opinions on our project and relies heavily on the work we did together as well as the project report that we all wrote and submitted together, but I am not by any means speaking for our group as a whole, or taking full credit for our project, which we did collaboratively and as a team. This article reflects my personal opinions and reflections only.*
D 在我读研的第一个学期,我与我的队友 Karmen Hutchinson、Kelsey Markey 和 Alene Rhea 在我们的数据科学导论课程中合作了一个机器学习项目。这是我与团队合作的第一批数据科学项目之一,我很感激有机会与上述人员一起工作。这是一次很棒的经历,这篇博文的发现是团队合作的结果。通过这篇文章,我希望与数据科学界的其他人分享我所学到的东西。特别是,尽管我们的团队没有找到结论性的结果,但我从寻找数据集、建立模型、评估模型以及与同行交流我们的发现的过程中学到了很多。让我们开始吧!
对于这个项目,我们的团队使用了来自 2019 年 12 月 2 日更新的数据集,这些数据集来自库克县开放数据门户上的[数据集](https://datacatalog. Cookcountyil.gov))。能够处理政府数据并理解政府机构跟踪心理健康的不同方法是很有趣的。该项目的目标是基于库克县的数据集,更好地了解和发现可能患有精神疾病的人。有趣的是,我们看到在这个数据集中,有精神疾病的个人在监狱中的人数据报道高达 30%,超过全国平均水平近 10%(行为健康创新,2015)。库克县在专业治疗法院和项目方面也处于领先地位,这些法院和项目可以及早识别符合条件的个人,并将他们与基于社区的服务联系起来,以增加成功的缓刑和重返社区的机会(TASC 健康与司法中心,2019 年)。然而,进入精神健康法院程序需要卫生部门的当前案例,并且发生在法律程序的相对较晚阶段。
这个项目之所以相关的一个重要原因是,患有精神疾病的人特别容易触犯法律。因此,他们在通过刑事司法系统时,往往需要专门的资源和周到的治疗。具体来说,当我们在这个项目中工作时,我们的团队旨在从一组司法和基于案例的特征中预测精神健康相关的倾向和判决,这些特征仅在初始阶段可用。早期发现可能患有精神疾病的人将使政府和其他机构能够尽早向这些人提供适当的支持。与普通人群相比,刑事司法系统涉及的个人中精神健康障碍的发生率要高三至六倍(Blandford & Osher,2012 年)。也有证据表明,患有精神健康疾病的人在监狱中度过的时间明显更长,并且在释放后一年内再次入狱的可能性接近两倍(Haneberg & Watts,2016 年;Eno Louden & Skeem 2011)。这为患有精神健康疾病的人创造了一个不利的环境,并形成了一个问题循环,他们被释放到社区,但很可能在未来又回到司法系统。该项目的目标是减少通过法律系统转移对精神健康障碍患者的有害影响,同时也最大限度地减少国家承担的费用。为了做到这一点,我们旨在预测一个人患有精神健康障碍的可能性,只要他们被引入法律系统(不需要医疗记录或人员)。审前识别个人允许迅速和适当的干预(即监狱转送干预)和资源(即强化案件管理方案,见 Loveland 等人,2007 年),以避免继续卷入法律系统。总的来说,我们的项目强调了在这样的系统中不同影响跟踪的需要。
本研究中使用的库克县法律数据集的描述。由项目团队成员创建的图像。
首先,让我们看看我们团队使用的数据集。库克县数据集包含多个标识号,这些标识号将它们之间的记录联系起来。因为我们想在个人层面上进行预测,所以我们使用了 case_participant_id。Case_participant_id 是库克郡分配给与案例相关的每个人的唯一内部标识符。每个 case_participant_id 可以与多个费用关联,每个费用在数据集中显示为单独的一行。如果发生了重新判决,一项指控可能会在判决数据集中显示为多行。此外,我们的团队还希望将我们的训练数据限制在 Initiation 中的 27 列,以便模拟用例。其中 14 列是分类的,6 列是基于时间的。
由于问题和数据集的性质,我们实际上创建了自己的目标变量。由于我们希望根据精神健康对个人进行分类,因此我们创建了一个二元目标变量“精神健康指标(MHI)”来表示个人是否被识别为患有精神健康残疾。我们查看了所有可能表明精神健康相关结果的量刑和处置的可能价值,并确定了 6 栏中的 15 个相关价值。我们将判决和处置数据集放在一起,以确定哪些行包含 MHI 的代理。例如,如果找到了这样的实例,则该个人有一个 sentence_type = “住院精神健康服务”,则该行的 MHI 为 1,否则为 0。由于可能有多行属于 case_participant_id,我们创建了一个单独的数据集,其中包含每个唯一的 case_participant_id 的一行,以及相应的 MHI。如果对应于该 ID 的任何行的 MHI 为 1,则每个唯一 ID 都被赋予 1。最终,我们能够分配 1 到 2212 个唯一的 case_participant_id 的 MHI。
由于年龄、种族和性别等受保护的类别可能会在模型中引入偏见,我们检查了它们在阳性和阴性标签实例中的分布。我们发现,阳性病例的年龄偏高,女性 MHI =1 的可能性几乎是男性的四倍,被标记为“混血儿”或“亚裔”的人跨种族的 MHI 阳性率最高,被标记为“西班牙裔”的人跨种族的 MHI 阳性率最低。
接下来,我们清理了数据集。在数据清理期间,我们更改了一些数据类型,填充缺失的信息,验证列中的输入是否一致,应用 StandardScaler()等缩放方法,以及 one hot 编码分类变量。具体来说,我们做了以下工作:
- 所有字符串变量都被转换成小写。
- 数字列(如 charge_count)被转换为整数。
- 所有缺失或异常的数值都被替换为中间值。
- 所有缺失的非数字输入都被替换为“未知”(因为数据调查显示这是库克郡已经实施的常见填充符。)
- 除“男性”和“女性”(即空值或“男性姓名,未给出性别”)之外的所有性别值都被转换为“未知”
- 年龄被转换成整数,空值和外围值(大于 100)被中值年龄所取代。在我们的伪基线随机森林表明年龄是一个极其重要的因素后,我们考虑建立一个单独的模型来预测和估算缺失的年龄。鉴于项目的时间限制,这被认为是不可行的。
- 转换为日期时间的日期和缺失或未知的日期被赋予一个对应于 1900 年 1 月 1 日午夜的填充值
- 因为关联热图显示 ID 号与重要特征相关,所以所有 ID 号都从数据中去除,以防止潜在的数据泄漏。Case_participant_id 设置为索引,而 case_id、charge_id 和 charge_version_id 从数据集中完全删除。
我们将所有分类变量转换为二进制虚拟变量,以允许使用参数模型,并为行的聚合做准备。这就产生了一个稀疏的高维数据集。
有“402”部分的案例参与者的频率。作者创建的图表。
因为我们使用的数据集非常不平衡,所以我们应用了缩减采样。为了帮助我们的模型学习识别数据集中的阳性类,我们使用随机分层采样对训练集中的阴性案例进行了缩减采样。考虑到对更大计算资源的访问,我们也想尝试一下上采样。具体来说,我们使用 100%的正面实例,并且不替换负面实例进行采样。最初,我们对负面实例进行缩减采样,使得正面实例占训练集人口的 50%,假设这将是我们的数据和用例的理想比率。但是,验证集和测试集没有缩减采样以复制部署。
数据集总体中和缩减采样后的类概率。作者创建的图像。
接下来,我们将数据集分成训练集、测试集和验证集。特别是,我们选择将数据集分为 70%数据的训练集、15%数据的静态验证集和 15%数据的测试集,这一比例得到了许多数据科学家的认可(Shah,2017)。为了模拟部署环境,其中我们的模型将用于预测未来时间,我们根据 received_date 划分了我们的训练、验证和测试集。接收日期最早的案例成为我们的训练集,最后的案例成为我们的测试集。我们想要使用基于时间的分区的一个原因是为了防止数据泄漏。
跨数据集的 MHI 基本汇率
为了降低数据的维度,在实施虚拟变量后,数据的维度增长到近 5000 个特征,我们使用了主成分分析,因为它简单、有效且可用于从数据集提取相关信息的非参数应用(Shlens,2014)。我们在我们的规模训练集上使用了 PCA 的经典应用(尽管稀疏 PCA 应该在未来的工作中进行研究),这产生了解释的方差比。给定更大的计算资源,我们希望使用 500 个主成分来执行测试,因为我们在该点看到一个尖锐的弯头。
在评估我们的模型时,我们关注两个性能指标:(1)受试者工作曲线下面积(AUC ),和(2)灵敏度。我们对此的推理是因为我们认为在我们的用例中,假阴性的成本要比假阳性的成本高得多;错过一个精神疾病的例子可能对那个人有害,但是向一个没有特别需求的人提供支持和服务可能只会给国家带来边际运营成本。然而,在没有库克县可用资源和心理健康相关预算限制的具体知识的情况下,我们试图提供一个可以在不同阈值下表现良好的模型。因此,我们针对 AUC 优化了我们的模型,密切关注每次迭代对灵敏度的影响。这种选择是基于这样的认识,即由于我们的基础率低,准确性将是一个很差的指标,因为它可能非常高,即使少数民族阶层没有得到很好的预测。另一方面,AUC 更适合我们的业务目标,因为它对类别不平衡很敏感,因为它对少数类别和多数类别一样重视。
解释主成分分析的差异率
优化 AUC 提供了一个自然的基线,因为 0.5 的 AUC 代表随机分配类别概率的模型(Brownlee,2019)。使用 50%的概率截止值,这种模型的预期灵敏度也将是 0.5。将该阈值降低到 0%(即,将每个病例分配到阳性类别)将是最大化灵敏度的最简单方法;事实上,这种模型的灵敏度是 1。这进一步说明了在这种情况下优化 AUC 比优化敏感性更好的原因:我们的目标不是简单地识别阳性实例,而是以最小的 I 型错误来进行。
我们运行的第一个模型是一个随机森林模型,具有现成的参数,适合我们清理和缩减采样的训练集。我们称之为“伪基线”,因为在这个阶段我们已经在数据管理上投入了大量的时间。基于系综树的方法被选为基线,因为已知它们在分类变量上表现良好(Tutz & Berger,2017)。在特征工程和超参数调整之前,我们将该模型视为开始的基线。这种未精炼的模型产生了 0.78 的 AUC 和 0.78 的灵敏度。
特征相关性
在我们的伪基线随机森林中展示的特征重要性指导了我们大部分的初始特征工程。例如,“Section 402©”属于前十五个重要特征,进一步的研究表明,这对应于与毒品或持有毒品相关的法律部分(伊利诺伊州州务卿)。因此,研究人员设计了一个新的指示变量,以编码 section 列是否包含 402©以外的其他“402”部分。为了关联附近的区域,我们将事件城市地理编码为纬度和经度。地理编码还确保我们的位置代理在整个数据集中是一致的。还设计了许多日期时间特性,试图更好地表示我们假设的心理健康事件和时间之间的相关关系。最后,由于事件发生时的年龄有近 4%的缺失值和大约 40 个(不现实的)超过 100 的异常年龄,我们为事件发生时的年龄是否为空和年龄是否超过 100 创建了一个二元特征。
就我们想要应用于这个问题的算法而言,我们确定了五种算法来探索:逻辑回归、决策树、随机森林、梯度推进和支持向量机。在所有特征工程完成后,使用已清理、已缩放和已缩减采样的数据的现成参数来建立每个模型的初始性能。
由于数据集的高维数,我们选择不使用 k-最近邻模型(kNN)进行实验。在这种情况下,事实上可能相似的实例可能有很大的距离,因此 kNN 可能表现不佳(Brownlee,2016)。我们还决定不实现朴素贝叶斯分类器,因为使用哑变量来编码分类数据明显违反了算法的条件独立性假设。
使用现成参数的验证集的性能指标
由于数据集的高维数,我们选择不使用 k-最近邻模型(kNN)进行实验。在这种情况下,事实上可能相似的实例可能有很大的距离,因此 kNN 可能表现不佳(Brownlee,2016)。我们还决定不实现朴素贝叶斯分类器,因为使用哑变量来编码分类数据明显违反了算法的条件独立性假设。
支持向量机 通常被视为机器学习的通用算法,我们选择 SVM 作为我们的探索模型之一,因为它能够通过线性或非线性内核捕捉复杂的关系。然而,SVM 比我们的任何其他模型花费更长的时间来训练,可能是因为我们的大量功能和支持 SVM 的约束优化问题(Ragnar,2016)。此外,它没有产生证明长时间训练是合理的结果。研究人员确定,开箱即用的 SVM 模型的运行时间和极低的灵敏度(0.46)意味着它不会成为超参数调整的候选对象。
逻辑回归 逻辑回归因其稳健性、可靠性和直观解释而被选中。此外,使用随机梯度下降的方法,逻辑模型相对容易用新数据更新,并且可以容易地被正则化以避免过度拟合(李,2017)。在进行适当的转换之后和调整之前,当使用所有~4800 特性时,模型无法收敛。增加最大迭代次数、测试不同的解算器以及测试不同的非线性特征变换都无法使模型收敛。假设多重共线性可能是一个问题,我们从随机森林中减少了前十个要素重要性的列数,并发现模型(使用 solver = 'liblinear '和 C = 1e30)能够拟合 AUC 为 0.72 的数据。我们最终决定不进行逻辑回归,因为很难将逻辑回归拟合到我们的虚拟变量,并且我们已经使用树方法得到了令人印象深刻的结果。
决策树
虽然集成方法通常在关键指标上优于决策树,但是单一决策树可以提供有价值的透明性。我们决定尝试创建可解释的决策树,因为在 hand⁴.问题的背景下,透明度尤其重要
验证集上的随机森林调优和性能
政府用来辅助决策的模型要接受公众的审查,因此提取一套直观的规则来解释其决策的能力可能值得降低绩效指标。树是根据未缩放的数据训练的,因此数值是可以解释的。研究人员迭代了 max_depth (2,3,4)、min_samples_leaf (1,10,100,500)和 max_features (10,5,3,无)的值。超参数的最佳组合是 max_depth=4,min_samples_leaf=10,max_features=None,AUC 为 0.75,灵敏度为 0.86。
随机森林 经过特征工程,我们用默认参数评估了一个随机森林模型,发现 AUC 上升到 0.79,灵敏度下降到 0.75。考虑到模型的高性能,我们决定继续调整随机森林模型,包括超参数调整和特征提取。我们从调整超参数开始,测试了一系列 max _ depths(无、3、10、30)、min_samples_leaf (2、50、100、200)和 n_estimators (10、100、500、1000)。我们发现在这些条件下,AUC 优化为 0.82,灵敏度= 0.78(表 A4)。然后,我们决定使用各种 PCA 组件进行调优,并使用 1、3、100 和 1000 的 PCA 组件以及不使用 PCA 的组件测试了相同的树参数。最终,没有 PCA 成分和超参数的组合可以击败没有 PCA 的模型性能。
梯度推进 考虑到我们的树集成方法的表现,我们决定将梯度推进作为我们的探索性算法之一。我们发现现成的梯度增强模型的 AUC 为 0.82,灵敏度为 0.76,因此是进一步超参数调整的合理候选。同时调整基于树和基于学习的超参数被证明成本太高,因此我们选择首先优化基于树的参数,然后使用所选择的参数来调整基于学习的参数。我们首先通过遍历 max_depth = [None,3,10,30]和 min_samples_leafs = [1,2,10,500]的所有可能组合来调整基于树的超参数(表 A5)。我们在这些条件下优化了 AUC,max_depth = 3,min_samples_leafs = 10,AUC = 0.82,灵敏度= 0.78。然后,我们使用这些参数迭代 n_estimators = [10,100,500,1000]和 learning_rates = [0.25,0.1,0.01,0.0001]。我们对使用如此大量的估计量感到满意,因为我们知道这不会导致梯度推进集成方法过度拟合。我们发现,当 learning_rate = 0.01 和 n_estimators = 1000 时,AUC 在这里得到优化,AUC = 0.82,灵敏度= 0.79。这个灵敏度比我们使用优化的随机森林获得的稍好,所以我们决定进一步优化梯度增强模型。
我们决定探索不同数量的 PCA 组件如何影响我们的梯度增强模型的性能。我们再次循环通过所有超参数,也在 1、3 和 100 之间改变组件数量。在使用较少组件评估模型时,我们能够创建一个大循环来优化主组件、基于树的超参数和基于学习的超参数的数量。所有超参数保持不变,只是增加了一个额外的 min _ samples _ leafs 100,以提高 10 到 500 之间的分析粒度。在 100 个主成分时,发现最佳 AUC 为 AUC = 0.81,灵敏度为 0.73。1 种成分的灵敏度优化为 0.91,然而该模型只有 0.55 的 AUC。
我们选择梯度推进集成算法作为我们的最终模型,其中{max_depth = 3,min_samples_leafs = 10,learning_rate = 0.01,n_estimators = 1000}
调整我们的梯度增强模型的最后一步是试验各种级别的下采样。所有以前的测试都依赖于 50%的下采样比率,因此我们的训练集具有相等的正负标签出现率。首先,我们使用 20%的下采样比率(意味着 20%的训练集由阳性实例组成)测试优化的梯度增强模型,并发现 AUC 几乎保持不变,但灵敏度极低,为 0.28。当模型以 10%的下采样比率再次运行时,灵敏度再次恶化到 0.10,因此我们得出结论,小于 50%的比率恶化了我们的模型性能,下采样比率在 50%时被优化。这些结果与我们关于下采样对灵敏度的影响的假设一致。
在这一轮评估中,我们选择不缩放我们的任何功能,因为缩放不应影响树模型(李,丁等人,2017)。为了测试这个假设,我们在未缩放的数据集上以 50%的下采样比率重新运行最终模型。这产生了与我们在缩放数据中发现的相同的 AUC 和灵敏度,证实了缩放是对计算能力的不必要使用。
使用我们之前的调整实验的结果,我们在没有 PCA 或缩放的情况下训练最终的梯度增强模型,并且具有 50%的下采样比率。我们使用最高性能的超参数 max_depth = 3,min_samples_leaf = 10,learning_rate = 0.01,n_estimator = 1000,并根据组合的测试和验证数据评估模型。我们的最终模型实现了 0.84 的 AUC,同时仍保持 0.76 的高灵敏度。接收器操作特征曲线表明,该模型相当快地实现了超过 90%的召回率,然后变得平坦。此时的假阳性率刚刚超过 40%。这一点代表最适合业务案例的分类阈值。
在部署该模型之前,有许多与偏差相关的问题需要解决。首先,我们必须解决我们的模型从数据中学到的社会偏见。我们的特征重要性充满了歧视性特征,考虑到 MHI 在这些阶层中的分布,这并不奇怪。可以采用区分单元测试来确定问题所在(d’Alessandro 等人,2019)。验证哪些特征与受保护的属性直接或间接相关,将允许研究人员识别哪些特征要从模型中移除。
因为我们的模型只能从法院已经确定的精神疾病案例中学习,所以它有效地让法院完全控制什么被认为是精神健康残疾。这样,MHI 只是精神疾病的一个松散的代表,这些记录中可能存在对某些类型或表现的精神疾病的偏见。人口统计学也可能有一个缺点,要么通常负担不起精神保健,要么被保健提供者系统地误诊,因为他们不会有残疾记录。先前的研究表明,某些人口统计数据不太可能被医疗保健专业人员认真对待(Hoffman 等人,2016 年),因此属于受保护群体的一些个人可能与他们确实存在的心理健康问题无关。为了捕捉这些隐性偏见的影响,种族的条目被尽可能少的改动。我们没有合并群体或过滤特定条目,因为如何看待一个人的种族可能会让我们了解这个人如何能够在司法系统中导航(Maryfield,2018 年),特别是在他们的心理健康方面。
我们惊讶地发现,与家庭暴力相关的特征没有在我们的模型中得到反映,因为研究表明,库克县的家庭暴力和精神健康障碍之间有着密切的关系(Tsirigotis & Luczak,2017;行为健康创新,2015)。领域相关的特征工程有可能捕捉到这种关系;还有一种可能是,这些精神疾病的案例通常没有得到法院的确认。
验证集上的梯度增强模型超参数调整
我们的模型是仅使用公开可用的数据开发的。如果库克县对这个项目感兴趣,我们可以与他们合作开发一个公平公正的精神疾病早期识别系统。这种系统可以利用不公开的信息,例如个人在库克县司法系统中的历史和他们的卫生部门记录。此外,我们的模式对于那些希望在法律体系内为个人提供服务的非政府组织来说仍然有希望。这里使用的数据是特定于库克县的,因此不能将模型直接导出到另一个辖区;然而,这个项目很容易成为其他地方类似项目的蓝本。
参数调谐梯度增强模型的 ROC
此外,概念漂移可能是部署中的一个问题。随着库克县精神健康法院项目的继续扩大,我们可以预计随着时间的推移,基本利率会增加,这最终可能会降低模型的性能。因此,应该仔细监控 MHI 基本利率以及可能影响它的法律和政策因素。当模型过时时,可能需要定期重新训练。模型管理人也可能决定从训练中排除较旧的数据,以减轻概念漂移——这可以通过经验进行测试,并需要结合审查偏差的影响进行考虑。1 型审查偏倚可能对我们的模型产生相反的影响,要减轻这种影响,需要为纳入训练的病例年龄开发一个启发式截断点。在删截的情况下,监控部署中的模型敏感性可能被证明是具有挑战性的,但是开发上述启发式方法将会给保管人一个设定的时间来评估个人的 MHI。
我们的模型目前不可实现,因为它依赖于受保护的类别(种族、年龄、性别)进行预测。在删除这些受保护的类和能够预测它们的功能时,需要进一步努力来测试模型性能。我们决定在我们的最终模型中保留敏感的特性,以免模型被误解为公平的,并强调不同影响跟踪的需要。
我和我的团队在这个项目中学到了很多东西,我很高兴将这些新想法应用到我即将到来的项目中!感谢阅读!
[1]可以在我们的团队 Github 上查看该项目中使用的功能的完整列表:https://github.com/angelaaaateng/dsga1001_project
[2]完整列表可以在我们的团队 Github 和团队报告上查看
[3]有关我们的数据清理流程的更多详细信息,请参见我们的最终报告以及我们的团队报告
[4]有关完整的决策树,请参见我们最终报告的附录,此处https://Github . com/angelaaaateng/dsga 1001 _ project/blob/master/TERM
应阿伦·雷亚和凯尔西·马基的要求,我附上了他们对这个项目https://github.com/akrhea/mental-health-court-outcomes所做的另一项研究的链接。
参考资料:
阿布舍克·夏尔马,阿布舍克·夏尔马。"决策树介绍及实例."geeks forgeeks2019 年 11 月 25 日https://www . geeks forgeeks . org/decision-tree-introduction-example/。
a .布兰德福德和 f .奥谢尔(2012 年)。*为有行为健康障碍的司法介入成年人实施循证实践和计划(EBPs)的清单。*纽约州德尔玛市:SAMHSA 行为健康和正义转化增益中心。
行为健康创新。库克县保释法院的精神健康与司法重罪保释法院精神病患者管理的审查。为伊利诺伊州法院行政办公室编写的报告,2015 年 7 月。
博特夫、兹德拉夫科和阿德·里德。“方差缩减。” Wiley StatsRef:统计参考在线,2017,第 1–6 页。,doi:10.1002/9781118445112 . stat 07975。
布朗利,杰森。“机器学习的 K 近邻。”机器学习掌握2019 年 8 月 12 日https://machinelementmastery . com/k-nearest-neighbors-for-Machine-Learning/。
布朗利,杰森。"评估 Python 中机器学习算法的度量标准."机器学习掌握,2019 年 11 月 21 日https://machinelingmastery . com/metrics-evaluate-Machine-Learning-algorithms-python/。
" TASC 健康与正义中心:连接政策、研究与实践."TASC 健康与司法中心|连接政策、研究和实践。、【http://www2.centerforhealthandjustice.org/】和。
认真分类:一个数据科学家的歧视意识分类指南。ArXiv.org,2019 年 7 月 21 日,https://arxiv.org/abs/1907.09013。
Eno Louden,j .,& Skeem,J. (2011 年)。患有精神障碍的假释犯:走向循证实践。循证矫正中心公报,7(1),1–9。
Haneberg,r .,& Watts,k .,“站出来”战胜美国监狱中的精神健康危机。刑事司法/教养。纽约州纽约市:州政府委员会司法中心。从http://knowledgecenter.csg.org/kc/system/files/Haneberg瓦 2016.pdf 取回
疼痛评估和治疗建议中的种族偏见,以及关于黑人和白人之间生物学差异的错误信念。美国国家科学院学报,美国国家科学院,2016 年 4 月 19 日,https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4843483/。
艾萨克斯,迈克。"县心理健康法院选择罪犯治疗而不是监禁."chicagotribune.com。2019 年 12 月 9 日接入。https://www . Chicago tribune . com/suburban/sko kie/CT-skr-mental-health-court-TL-0526-2016 05 23-story . html。
约翰斯通,伊恩 m 和亚瑟鲁愚。"关于高维主成分分析的一致性和稀疏性."美国统计协会杂志,第 104 卷,第 486 期,2019 年,第 682–693 页。,doi:10.1198/jasa.2009.0121。
《隐含的种族偏见》法律中隐含的种族偏见,第 9-24 页。,doi:10.1017/CBO 9780511820595.002。
洛夫兰、大卫和迈克尔·博伊尔。“强化个案管理作为一种监狱转移计划,用于患有严重精神疾病的人.”《国际罪犯治疗和比较犯罪学杂志》,第 51 卷,第 2 期,2007 年,第 130-150 页。,doi:10.1177/0306624x06287645。
李,惠,和 SAS 数据科学博客。“我应该使用哪种机器学习算法?”SAS 数据科学博客,2017 年 4 月 12 日,https://blogs . SAS . com/content/subconcious musings/2017/04/12/machine-learning-algorithm-use/# pretty photo。
李,丁,等.〈自适应标度〉. ArXiv:1709.00566v1【统计。ML]2017 年 9 月 2 日2017。
玛丽菲尔德贝利。“隐含的种族偏见。”司法研究与统计协会,2018。
“现代机器学习算法:优势和劣势。” EliteDataScience ,2019 年 1 月 25 日https://elitedatascience.com/machine-learning-algorithms。
纳格帕尔,阿努哈。"主成分分析-简介."中,走向数据科学,2017 年 11 月 22 日,https://towardsdatascience . com/principal-component-analysis-intro-61f 236064 b38。
萨朗·纳克赫德。“理解 AUC-ROC 曲线。”中,走向数据科学,2019 年 5 月 26 日,https://towardsdatascience . com/understanding-AUC-roc-curve-68b 2303 cc9 C5。
理解和使用敏感性、特异性和预测值。https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2636062/眼科杂志,Medknow 出版物,2008 年,印。
亚历山大·普拉里卡斯(1998 年 9 月)。信号处理公式和表格手册(第 1 版。).CRC 出版社。第 42–8 页https://en.wikipedia.org/wiki/Upsampling#cite_note-1
拉格纳。"什么样的学习问题适合支持向量机?"数据科学栈交换,2016 年 2 月 1 日https://Data Science . Stack Exchange . com/questions/9736/what-kinds-of-learning-problems-is-fitted-for-support-vector-machines。
" Sklearn.metrics.recall_score。" Scikit ,https://Scikit-learn . org/stable/modules/generated/sk learn . metrics . recall _ score . html。
塔朗·沙阿。"关于机器学习中的训练、验证和测试集."介质,朝向数据
科学,2017 年 12 月 10 日,https://towards data science . com/train-validation-and-test-sets-72 CB 40 CBA 9 e 7。
黄邦贤史伦斯。"主成分分析教程."ArXiv:1404.1100 v1【Cs。2014 年 4 月 3 日、https://arxiv.org/pdf/1404.1100.pdf。
伊利诺伊州国务卿。“在线服务。”伊利诺伊州国务卿、https://www.cyberdriveillinois.com/的官方网站。
Tsirigotis、Konstantinos 和 Joanna UC zak。“遭受家庭暴力的女性的复原力。”《精神病学季刊》,斯普林格美国,2018 年 3 月,https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5807488/。
图茨,格哈德和莫里茨·伯杰。"广义加性回归中分类预测因子的树形结构模型."数据分析与分类进展,第 12 卷第 3 期,2017 年,第 737–758 页。,doi:10.1007/S11 634–017–0298–6。
托尼,你好。“了解随机森林”中,走向数据科学,2019 年 8 月 14 日,https://Towards Data Science . com/understanding-random-forest-58381 e 0602d 2。
使用 Pandas 和 Plotly 分析冠状病毒(新冠肺炎)数据
透过总体数字来量化冠状病毒的传播
起源于中国的新冠肺炎病毒现已蔓延至全球,各国都在争相应对。这种病毒最初是一种医疗保健紧急情况,现在已经开始产生严重的经济后果。在这篇文章中,我试图用熊猫和情节来理解和想象新冠肺炎的传播。
我们从导入所有库和加载所有数据开始。这些数据由约翰·霍普斯金大学提供,可以在这里的链接中找到。有三个文件包含全部确诊病例、死亡和恢复情况。
运行上面的代码后,数据看起来是这样的。
我们有
- 日期(作为列)
- 确诊病例总数
- 省
- 国家
- 经纬度
尽管数据组织得很好,我们还是会对其进行一些修改,使其更适合绘图。我们可以使用 pandas melt 函数来改变一列中的日期,这样我们可以在以后将它设置为索引,这使得绘图更加容易。
有了日期和国家作为索引,数据集现在更清晰、更容易绘制。请看下面的快照。
出于本文的目的,我们将只查看国家级别的数据,而不是省/州级别的数据。让我们创建一个整合的数据集,将病例、死亡和恢复的数据集结合起来。我还创建了一个从累积数据中获取每日计数的函数。我们使用下面的代码进行处理。
我还创建了一些计算字段,如活动病例(没有死亡或康复结果的病例)和死亡病例比率,并使用 pandas 的合并功能合并了所有数据集。
活跃病例=确诊病例-康复-死亡
整合数据集的索引为国家和日期,每个指标为一列(参见下面的快照)
我们使用 plotly.graph_objects 进行所有的绘图。这是一个简洁的解决方案,不用很多代码就能做出漂亮的图形。我们使用下面的代码创建五个图表,显示全球确诊病例、活动病例、死亡、恢复、死亡与病例比率的数字。
全球 Covid19 案例的关键指标
我们可以看到,确诊病例、死亡和康复都在增长,但很难从这些数字中得出增长的程度,因为这些数字是累积的。死亡与病例的比率为 3.4%(这是世卫组织几天前报道的)。
“活动案例”作为一种衡量标准的效用是显而易见的,因为它提供了给定时间点的爆发快照。从图表中,我们可以看到,自 2 月中旬以来,活跃病例一直在下降,这是一个很好的指标,表明我们正在更好地控制疫情。然而,这张图表本身就有误导性,因为我们知道大多数病例发生在中国,这可能会扭曲数据。让我们比较一下中国以外地区的活跃案例。
上面的情节描绘了中国内外两种截然不同的情况。随着中国国内病例数量的下降,疫情似乎得到了控制,但在中国以外,疫情仍在强劲增长。
让我们进一步挖掘每个国家的数据。为了使数据有意义,让我们把自己限制在病例都大于 300 的前 10 个国家。让我们使用下面的代码,根据案例的数量来绘制这些国家的图表:
截至 2020 年 3 月 5 日的数据
现在让我们来看看前四个国家的病例是如何随着时间的推移而增长的。使用下面的代码,我们计算从“疫情开始”起的病例数,疫情开始是指报告第一例病例的那一天
例如,下面意大利的输出显示,第一个日期“2020-01-31”是两例确诊病例爆发的开始。该指数可以解释为“第 0 天”。
现在,让我们根据病例数绘制出前四个国家的数据,并使用以下代码查看疫情是如何随着时间的推移而增长的:
让我们看看下面的输出。请注意,一旦开始增长,数字增长得有多快。
这些国家的疫情都是逐渐开始的,但一旦达到“爆发”阈值,就会呈指数级增长。上面的图表是对自满的警告。如果不加以控制,病例会非常迅速地增加,增长几乎是指数级的,因此即使是少量的病例也可能迅速发展成全面爆发。
在韩国跨过 100 例用了 29 天,跨过 7000 例只用了 16 天!在意大利,跨过 100 用了 23 天,跨过 5000 只用了 13 天!
最后,由于不同国家的情况差异很大,因此创建一个视图来深入了解特定国家是值得的。在这里,我们可以使用 ipywidgets 库使我们的图表具有交互性,并使用下面的代码从下拉列表中选择国家:
下面的输出与上面的非常相似,但是我们现在有一个下拉菜单,可以从中选择国家(图表显示的是意大利)。
我们再次注意到,增长是突然的,而且相当迅速,尽管病例数量很高,但就病例总数而言,意大利仍未达到峰值!
全世界的病例和死亡人数一直在增长,但我们可以从中国的数据中看到,如果采取适当的措施,疫情是可以得到控制的。如果你想用我的代码自己分析数据,请在这里找到 jupyter 笔记本的链接。
让我们希望并祈祷疫情迅速结束!
分析用于图像分类的数据扩充
最新图像分类模型中使用的图像增强技术的主成分分析
图像分类是机器学习中研究最多、记录最完整的任务之一。有许多基准和大型公共数据集,如 ImageNet [1],可以将新的模型和算法与最先进的技术(SOTA)进行比较。每年都会有一些新算法问世,SOTA 的精确度也在快速提高。
近年来,改进的一个关键因素是数据扩充技术,特别是当人们试图在不使用外部数据的情况下改进排名时。数据扩充,对训练数据的微小修改有助于模型更好地概括未知的例子。数据增强处理图像的一个例子是镜像图片:猫也是镜子里的猫。
在这个故事中,我们分析了谷歌研究人员 2019 年的算法 RandAugment [2]中使用的图像增强技术。为此,我们将使用 Tensorflow 2.0 Keras 中提供的预训练 DenseNet [3]模型。为了说明模型输出,我们将对模型的最后一个隐藏层进行主成分分析。所有代码都可以在 Google Colab 上找到。
ImageNet 分类基准的改进由代码为的论文说明
图像分类
有了 Keras ,图像分类就是一个三步走的问题。1)加载图像,2)加载预训练的模型,3)解码输出。下面是使用 TensorFlow 2.0 预训练的 Keras DenseNet 模型来完成它的一个小片段。
Keras 中基于预训练模型的图像分类
如果我们用include_top
加载模型,分类的输出层有 1000 个类。decode_predictions
收集最有可能的类别,并添加类名。
猫图像来自维基百科并分类输出
对于 PCA 分析,我们将使用模型的最后一个隐藏层的输出(在 softmax 之前)。在 DenseNet 121 中,它意味着 1024 维的大向量空间。在 Keras,我们将使用以下方式获得新模型:
model_last_hidden = tf.keras.models.Model(inputs=model.input, outputs=model.layers[-2].output)
我使用这个实践来获得模型输出的良好表示,而没有 softmax 层的展平效果。
原始论文中 DenseNet 模型的一个例子
利用主成分分析子空间识别类别
主成分分析
正如我在前面的故事中所讨论的, PCA 是一种正交变换,我们将使用它来降低向量的维数【4,5】。PCA 以最大化降维数据的方差的方式找到投影的特殊基向量(特征向量)。使用 PCA 有两个重要的好处。一方面,我们可以将 1024 维向量投影到 2D 子空间,在那里我们可以将其绘制成数据。另一方面,它保持了最大可能方差(投影丢失了信息)。因此,它可能保持足够的方差,以便我们可以识别图片上的类。
最大化第一主成分的方差—来自维基百科的等式
使用带有[sklearn.decomposition.PCA](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html)
的 PCA 是一行程序:
pred_pca = PCA(n_components=2).fit_transform(pred)
猫还是大象?
为了确定从 1024 维向量空间到 2D 向量空间的变换,我们将使用八幅图像,四只猫和四只大象。稍后,我们将在这个投影中展示数据增强的效果。图片来自维基百科。
维基百科上的猫和大象的图片
举例说明 2D 投影,我们可以看到猫和大象是完全分开的。
使用主成分分析的 DenseNet 最后隐藏层的 2D 投影
图像增强
数据扩充是训练机器学习模型的重要部分,尤其是在训练图像有限的情况下。对于图像增强,定义了许多增强算法。Python 开发者可以在imgaug
包中找到大量的方法。
对于此分析,我们将使用 RandAugment [2]中使用的方法的imgaug
实现,这是一种使用 EfficientNet 模型在 2019 年实现 ImageNet 上 SOTA 的增强,但许多其他算法也使用相同的基本方法。
对于数据扩充,最关键的部分是确定扩充方法的参数区间。例如,如果我们使用旋转这种简单的图像增强技术,很明显,将猫或大象的图像旋转几度不会改变图片的含义。然而,通常情况下,我们并不希望看到大象在自然界中旋转 180 度。另一个例子是当我们使用亮度或对比度操作时:过高的修改可能会导致无法识别的数据。
最近的工作,如基于人口的扩增[6]旨在适应训练时间的修改幅度,而其他人如 RandAugment [2]将其用作训练的超参数。自动增强研究表明,在训练期间增加增强的幅度可以提高模型的性能。
使用 imgaug 增强方法的默认间隔来增强大象图像
如果我们处理上面的增强图像,并将其投影到与之前的猫象图像相同的 2D 向量空间,我们可以看到新的点在原始图像的周围。这是图像增强的效果:
增强将分类空间中大象图像的单点扩展到大象图像的整个区域。
猫-大象投影中的增强大象图像
一次性方法
当一个标签类中只有很少的样本时,这个问题被称为少镜头学习,数据扩充是解决这个问题的一个重要工具。下面的实验试图证明这个概念。当然,这里我们有一个大数据集上的预训练模型,所以它不是在几次训练中学会的。然而,如果我们试图只使用一个原始的大象图像来生成投影,我们可以得到类似的东西。
对于这个投影,我们将使用一个新的 PCA,它使用原始的大象图像和它的增强图像。下图显示了该子空间中的增强图像。
增强图像的 PCA 投影
但是这个投影,仅仅使用一张大象图片,能把大象和猫分开吗?嗯,聚类不像前一种情况那样清晰(见第一个散点图),但猫和大象实际上在向量空间的不同部分。
从一幅大象图像生成的 PCA 投影中的大象和猫
摘要
在这个故事中,我们展示了在最先进的影像分类中使用的数据增强工具的效果。我们将猫和大象的图像以及大象的增强图像可视化,以更好地理解模型如何看待增强图像。
参考
[1]邓,李,李,,李(2009 年 6 月).Imagenet:一个大规模的分层图像数据库。在 2009 年 IEEE 计算机视觉和模式识别会议上(第 248–255 页)。Ieee。
[2] Cubuk,E. D .,Zoph,b .,Shlens,j .,& Le,Q. V. (2020 年)。 Randaugment:用缩小的搜索空间进行实用的自动数据扩充。在IEEE/CVF 计算机视觉和模式识别研讨会会议录(第 702–703 页)。
[3]黄,g .,刘,z .,范德马腾,l .,,温伯格,K. Q. (2017 年)。密集连接的卷积网络。IEEE 计算机视觉和模式识别会议论文集(第 4700–4708 页)。
[4]皮尔逊,K. (1901 年)。 LIII。在最接近空间点系统的直线和平面上。 《伦敦、爱丁堡和都柏林哲学杂志和科学杂志》, 2 (11),559–572 页。
[5]h .霍特林(1933 年)。将复杂的统计变量分析成主要成分。教育心理学杂志, 24 (6),417 页。
[6]何,d,梁,e,陈,x,,I .,& Abbeel,P. (2019 年 5 月)。基于人口的扩增:扩增策略时间表的有效学习。在机器学习国际会议*(第 2731–2741 页)。*
分析 Docker 图像安全性
Docker 容器并不像许多人认为的那样本质上是安全的。因此,使用工具和扫描仪来清除容器中的漏洞至关重要…
很多人认为 Docker 图像和容器在默认情况下是安全的,不幸的是,事实并非如此。有很多因素会影响 Docker 图像的安全性。无论是安装在映像中的包、应用程序使用的库,甚至是基本映像,所有这些组件都可能给应用程序带来漏洞。这些问题中有很多是很容易避免的,尽管…
安克雷奇-克莱尔公司
在 Docker 映像中查找漏洞的最简单方法是使用或等工具对其进行检查:
- Anchore Engine:Anchore是集装箱图像检查、分析和认证的集中服务。它使用来自操作系统供应商的漏洞数据(feeds)扫描图像,如 Red Hat 、 Debian 或 Alpine 。对于非 OS 数据,它使用 NVD(国家漏洞数据库),包括 RPM 、 Deb 、 APK 以及 Python (PIP) 、 Ruby Gems 等漏洞。
- Clair : Clair 是由 CoreOS 为码头工人和 APPC 集装箱开发的静态分析仪。它使用来自类似来源的漏洞元数据Anchore——红帽安全数据、NVD、Ubuntu CVE 追踪器、Alpine SecDB、Debian 安全漏洞追踪器等。
安装
现在我们知道了我们想要使用的工具,是时候旋转它们了。 Anchore 和 Clair 都包含各种集成,可以部署到 Kubernetes 或 OpenShift 上,但是为了演示的目的,我们将在本地机器上使用docker-compose
来设置它们:
要设置锚定器,运行以下程序:
要设置克莱尔运行以下程序:
这样我们就可以开始分析了!
注意:我们将在本文稍后回到 Clair。
检查图像的漏洞
让我们从 Anchore 和基本 Debian 镜像开始。为了分析我们的图像,我们需要做的是add
它和wait
来完成分析:
分析图像后,让我们看看发现了哪些漏洞:
我们首先运行带有all
标志的image vuln
命令,列出镜像中存在的操作系统和软件包漏洞。所有这些都是Negligible
或Unknown
,所以这很好。接下来,我们运行evaluate check
命令来查看这个映像是否通过了默认策略检查,正如您在上面所看到的(Status: pass
),它通过了。在这方面,这些类型的基本图像通常表现很好,因为它们被广泛使用,因此受到了大量的审查。
但是,使用 Python 3 Debian Buster 映像构建的简单的 Python 应用程序呢?让我们先看看 docker 文件:
没什么叫*【脆弱】**【不安全】*的吧?那么,让我们构建并分析它:
我首先使用上面的debian.Dockerfile
和简单的hello.py
构建图像。然后我把它推到 Docker Hub,从那里它被添加到 Anchore 并最终被分析。现在我们可以看看结果:
嗯,嗯,嗯…不再那么好了。我们所做的,就是切换到官方 Python Debian 映像,我们突然有了 1000 多个漏洞,包括一些Low
和Medium
严重性的漏洞,相比之下,只有几个Negligible
有普通的 Debian 映像。最重要的是,在运行映像评估后,我们可以看到它失败了(Status: fail
)。
所以,即使我们使用了官方的看似安全的图片,并且创建了没有明显漏洞的非常简单的应用程序,我们仍然有很多安全问题。那么,我们该如何改善这一点呢?
寻找卓越的形象
说到安全性,基本映像的最佳选择是scratch
——也就是空容器。然而,从零开始构建我们自己的基础映像是不太实际的,而且考虑到我们大多数人都不是安全专家,这可能会带来更多的安全问题。
在我看来,排在scratch
之后的第二个最佳选择是发行版,它是由谷歌制作的一组图像,创建这些图像的目的是为了安全。这些图像包含您的应用程序所需的最少内容,这意味着没有外壳、包管理器或任何其他工具会使图像膨胀,并为安全扫描仪(如 CVE )产生信号噪声,从而更难建立合规性。我已经在我之前的博客文章这里中更详细地介绍了发行版,所以如果你想知道更多,去看看吧。
好吧,那么distrolles现在是我们安全映像的选择,但是在我们分析任何东西之前,让我们先来看看新的Dockerfile
:
与 T21 的 Debian 版本相比,没有太大的变化。我们真的只是把赛跑运动员的图像换成了gcr.io/distroless/python3
。现在,我们可以继续构建它,推动它并将其添加到 Anchore Engine :
最后,是时候看看发行版是否比 Debian 版本表现得更好了:
的确如此!与第一个例子相比,我们只有 53 个漏洞和 2 个Low
严重性漏洞。还有,看政策评估——这个镜像通过了,而 Debian 那个失败了!
因此,我们可以有把握地得出结论,当谈到我们的容器化应用程序的安全性时,distroles是更好的选择。但是,我们还可以检查和改进一些东西,例如,我们可以尝试使用不同的评估策略:
上面的命令列出了我们可以用来检查图像的所有可用策略。默认情况下,它使用anchore_default_bundle
,这很好,但是如果我们想看到使用不同的白名单和规则执行检查,那么我们可以尝试,例如anchore_cis_1.13.0_base
:
从上面可以看到,我们首先通过运行policy hub get
来查看这个特定策略的描述。之后,我们安装它并运行policy list
命令,这表明我们现在有 2 个策略可用,新策略处于非活动状态。所以,我们用最后一个命令(policy activate
)激活它。
在我们对新政策进行检查之前,让我们先谈谈它包括哪些内容。考虑到是 Docker CIS 1.13.0 检查,将重点关注本文件提供的指导。举几个例子来说明它在寻找什么样的问题:
ADD
用于代替COPY
的命令- 未列入白名单的开放端口
- 失踪
HEALTCHECK
- 使用不可信的基础映像
- 使用
root
作为有效用户
您可以运行anchore-cli --json policy hub get anchore_cis_1.13.0_base
获得该政策中规则的完整列表。现在,我们知道了它将寻找什么,是时候运行它了:
在一些已安装的软件包的输出中有相当多的警告,但是特定于这个策略的是上面列出的那些。这些是使映像无法通过策略评估的问题/漏洞,应该进行分析。其中一个(第二个)是由于使用了发行版映像,因为它不在白名单中,所以可以忽略。至于其他两个——这些都是应该解决的实际问题,但它们都有简单明了的解决方案。
这向我们表明,根据我们的需求选择特定的策略,甚至使用多个策略来捕捉尽可能多的问题是一个好主意。
漏洞的类型/级别
然而,并非所有的漏洞都是相同的,其中一些甚至不适用于我们的应用程序/容器/环境。因此,我们不仅要看它们的严重程度,还要看计算严重程度的因素。这包括攻击媒介、攻击复杂性、保密性影响、完整性影响等。然后,这些因素会创建使用 CVSS 计算器生成的最终严重性分数。具体的括号有— None
、Low
、Medium
、High
和Critical
。关于他们的更多信息可以在 https://nvd.nist.gov/vuln-metrics/cvss 的 NIST 网站找到。
修复漏洞
上例中的漏洞很容易修复,但情况并非总是如此。您可能会遇到严重性分数较高漏洞,或者对于您的特定用例来说有问题的漏洞。当涉及到基础映像或其中包含的包的问题时,你不应该修复它们,因为那是所述工具的开发者和/或发行者的责任。也就是说,您应该通过移除攻击媒介来防止或至少减轻现有和未来漏洞的可利用性,例如通过使用没有任何外壳的发行版。最重要的是,使用构建/部署管道定期运行漏洞检查是一个好主意,以便在引入漏洞时尽快发现漏洞,最好使用多种扫描工具。
Anchore 对 Clair
说到运行多种扫描工具…那么克莱尔呢?它与主播相比如何?这就是我们如何针对我们的示例 Debian 和发行版映像运行它:
查看以上命令的输出,它看起来与我们从 Anchore 那里得到的非常相似——Debian 镜像有很多问题,而发行版没有任何问题。在第一次扫描的输出中,有一个我省略了的巨大的漏洞列表,但是我检查了所有的Low
和Medium
严重性漏洞,它们同时出现在 Anchore 和 Clair 扫描结果中,但是情况可能并不总是如此。因此,我建议运行多种扫描工具,最好是使用不同来源的漏洞元数据的扫描工具。
你可能想看看的一些其他工具包括 Docker Bench for Security 或 IBM Container Registry 中包含的漏洞扫描。
结论
对于与安全相关的东西,最好是积极主动,在漏洞变成实际问题之前尽量避免它们,本文中展示的工具可以在这方面提供很大帮助。我认为使用和实现这个工具非常容易,所以任何人都可以让它们成为日常工作流程的一部分,或者理想情况下,成为他们自动化管道/工作的一部分。在一定程度上,我们创建的应用程序的安全性是每个开发人员的责任,而不仅仅是安全专家或测试人员的责任,所以我们都应该参与到使它们足够安全的工作中来,也许只是通过不时地运行漏洞扫描。😉
本文最初发布于martinheinz . dev
注来自《走向数据科学》的编辑: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
通过 Python 中的可视化和机器学习分析电动滑板车活动
从数据收集到在热图上可视化聚类
根据最近发布的 2019 TomTom 交通指数报告,全球 416 个城市中有 239 个城市(57%)交通拥堵加剧。仅在美国,主要城市的交通拥堵率为+20%。这意味着,一次出行将比在城市基线不拥挤的情况下多花 20%的时间。洛杉矶以+42%高居榜首,然后是+37%的纽约市。更不用说由于更高的排放量而带来的环境影响了。
如上所述,人们正在使用替代的通勤方式,如拼车、骑自行车、公共交通或两者的结合。一个这样的选择是电动滑板车。电动滑板车对市场来说相当新,主要针对通勤的“最后一英里”部分。
伯德公司就是这样一家公司。对于那些不熟悉该公司的人,伯德在全球 100 多个城市经营无码头滑板车。有各种型号的滑板车,在我去华盛顿特区实地考察的时候,我就骑过其中的两种。骑滑板车不贵,除非你的平衡感很差,否则骑这种车应该很容易。
来源(左):CBINSIGHTS(右):NACTO
停放在华盛顿特区十字路口的小型摩托车
简介:
踏板车通常安装有大量传感器,这导致大量数据产生,因此有利于有趣的数据探索。
我带着以下问题出发:
1.电动滑板车在华盛顿特区及其周边的普及程度如何?
2.平均行程距离是多少?用法是否取决于一周中的日期和时间?后者看似直观但仍需证明。
3.我能从顾客使用滑板车的行为中了解到什么?
4.我可以用上面的发现来产生见解吗?
数据收集:
某些州要求公司制定开放数据政策。但是华盛顿特区没有,因此 Bird 没有一个面向华盛顿特区的公共 API,但是一个在 Github 上化名为 ubahnverleih 的人设法一起破解并开发了一个 API。ubahnverleih 已经为多个其他乘车共享公司开发了 API,所以如果你正在寻找任何这样的数据,请查看他的 Github。
华盛顿特区及其周边地区大约占地 100 平方英里。为了覆盖整个区域,我精心选择了 49 个不同的坐标点*(大致 36 sq。这样,在 API 中 ping 这些点,将返回该地区所有小型摩托车的数据。我使用的方法可以在这里找到。*
图片来源(右):波导手机 app
这是任务的第一部分。然而,它缺少一个要素:时间。我想研究一段时间内踏板车的使用情况,而不仅仅是踏板车在地理上是如何分布的。
为了捕捉一段时间内踏板车的使用情况,我编写了一个脚本,每隔 5 分钟运行一次,收集上面提到的所有坐标点的踏板车数据。我让这个程序运行了 1.5 个月(48 天),产生了大约 800 万个数据点。
数据收集挑战:
该项目的 API 争论阶段存在许多技术挑战,虽然我不会一一列出,但我认为特别提到其中一个是有帮助的,可以说明在野外收集数据时会出现的各种挑战:
- 我写了一个脚本,收集了一整天的数据。在启动收集过程后,我注意到程序已经连续两个早上崩溃了。
- 我的第一反应是认为这是由于代码中的一个错误,但即使在检查之后,也没有发现是什么导致了这个问题。
- 然后我检查了收集的数据,注意到一个模式,崩溃前每晚的最后一次 API 调用总是在*【第二天】*凌晨 12 点之前的某个时间。当时没有对这个问题提供任何见解,但这是了解问题原因的一个小线索。在对代码进行了大量修补和不成功的测试后,我决定通宵坐着亲自见证崩溃。
- 这种方法,以及随后的黑客侦探工作,让我发现 API 在每天晚上 12 点到凌晨 4 点之间不会返回任何东西,而是在凌晨 4 点以后开始工作。公司经常对他们的系统进行维护,有些比其他公司更频繁,这可能是一个原因。无论如何,在知道这一点后,我在脚本中添加了一个小部分来说明每晚的事件,这是一个简单的解决方法。但这无疑是一个具有挑战性的诊断。
有了现在定期收集的数据,我创建了一个 PostgreSQL 数据库来更好地组织和管理数据。从我的 Jupyter 笔记本中添加了另一个脚本部分来做同样的事情。
数据:
在经历了上述步骤并克服了挑战之后,下面是结果数据的样子。
每一行包含一个单独的踏板车的所有相关信息。
例如,在上面快照的第一行,小型摩托车‘3db0f8a8–72ed-4d4b-aff8-f0f2de53c875’
,型号为‘bd’
,位于4:00:53 AM
的10/3/19
上的(38.927739,-77.097905)
,电池电量为42
,这意味着它将运行另外5069
个单位*(大约 6 英里,根据数据比例)*。
使用数据发现的踏板车型号“bd”。(厂商网址:http://www.electisan.com/)
有一些空的和多余的字段,它们没有用,因此我的分析没有考虑它们。
分析策略:
以下是我回答上述问题的方法概述。
我已经在帖子的末尾浏览了结果。
- 数据清理。
- 从收集的数据中创建踏板车出行数据集,并使用它来研究使用和活动,同时也有助于进一步分析。
- 定义检测踏板车何时静止和何时移动的规则,以便分析和比较使用活动。
- 使用上述规则的特征工程和对踏板车位置的聚类来跟踪聚类中心。
为了这篇博文,我将更多地关注方法和结果,而不会过多地探究背后的代码。如果需要,我会展示某些代码部分。完整的代码可以在我的 GitHub 这里找到。
1.数据清理
双重:
A.修复数据类型。
B.创建一个 datetime 功能,以允许对数据进行切片和切块。
我创建了一个助手函数来为我们完成上述工作…
def fix_dtypes(df):
int_cols = [‘battery_level’, ‘estimated_range’]
float_cols = [‘latitude’, ‘longitude’]
df = df.astype({col: ‘int32’ for col in int_cols})
df = df.astype({col: ‘float32’ for col in float_cols})
#Combining date, time column to datetime
df[‘date_time’] = df[‘Date’].map(str) + “ “ + df[“Time”].map(str)
df[‘date_time’] = pd.to_datetime(df[‘date_time’])
return dfdf_scootsbydate = df_multiple_days.groupby(‘Date’ [‘id’].nunique()
df_scootsbydate.mean()[Out]: 5337.555555555556So, on any given day, there are about 5338 scooters spread throughout the D.C. region.
2.踏板车旅行:
原始数据实际上是每次 ping API 时拍摄的一系列快照。每次 ping 都会返回当时所有可用指示器的数据。
在一个非常简单的层面上,人们可以想到一本翻书动画。就像每个页面是一个快照,所有页面放在一起向我们显示从开始到结束的整个序列,第一页显示序列的开始,最后一页显示序列的结束。类似地,第一个 API 调用将显示一天开始时踏板车的时间和位置,最后一个 API 调用将显示一天结束时的时间和位置。
下面是结果数据的一个片段,以及我是如何完成的。
在不涉及太多技术细节的情况下,(‘latitude_first, longitude_first)
是在‘date_time_min’
日期&时间投入运行时的坐标,(‘latitude_last, longitude_last)
是在‘date_time_max’
日期&时间停止运行时的坐标。
‘time_diff_hrs’
:滑板车一天的总运行时间。其被计算为仅仅‘date_time_max’ — ‘date_time_min’
。
计算行驶距离( **‘distance_miles')**
:
‘distance_miles’
:滑板车一天行驶的距离(英里)。
从上面,我们有(‘latitude_first’, ‘longitude_first’)
和(‘latitude_last’, ‘longitude_last’)
为每辆踏板车。现在我们需要计算这两个位置之间的距离。有几种方法可以计算两点之间的距离。但是在这里,由于我们处理的是 GPS 坐标,我们使用 哈弗森距离 进行计算。哈弗辛考虑了地球的球形,而像欧几里德距离这样的公式却没有考虑,这就是为什么哈弗辛适合在这种情况下使用*。*
有一些好的 Python 包可以做到这一点。我使用这个资源来构建一个助手函数来为我完成这项工作。
3.平稳性和非平稳性的定义规则。
在这一点上,我们有了收集数据的 1.5 个月中每天的踏板车出行数据集。我们现在可以分析踏板车的用法和运动。但是这并不像仅仅根据距离特征过滤指示器那么简单,换句话说,子集化数据以只选择那些‘distance_miles’
是0
的指示器。
**Stationary**
:当满足*【Cond _ 1】和**【Cond _ 2】时,认为 Scooter 是静止的。
**Non-stationary**
:如果不满足**'Cond _ 2】但满足【Cond _ 3】,则认为 Scooter 是非静止的(或在行程中)。也就是说,如果‘distance_miles’
大于 0.5 英里并且‘battery_diff’
小于 50 英里(下面给出解释)。***
有一些挑战,我解释了我用来识别踏板车是否静止的三个条件:
1.Cond _ 1:‘time_diff’
应该大于 4 小时。有很多情况下,滑板车会在白天出现一小段时间,然后消失几天。可能是由于各种原因,如维修、保养等。这篇文章在某种程度上证实了这一点。滑板车的消失并不真正算作滑板车的使用活动,因此没有计入我们的分析。这一规则使得重点放在“肯定”是静止的踏板车上。
2
2。 Cond_2 : ‘distance_miles’
应该小于 0.5 英里。人们可能会猜测,要将小型摩托车视为静止,您所需要的就是距离特征为零,对吗?好吧,数据有一些不一致的地方,我们注意到有些情况下滑板车移动了很小的距离,不到几码左右。几码并不真正算作“旅行”,所以我们将阈值保持在 0.5,以避免在我们的分析中采取这些滑板车运动。
3. Cond_3 : ‘battery_diff’
应该小于 50。这是一个偷偷摸摸的方法,因为这是在分析的几次迭代之后实现的。有时候,滑板车被拿出来充电后,会被放回一个完全不同的地方。我们最初的分析认为这是踏板车运动,但在认识到这种不规则性后,使用这种条件有助于我们避免犯这种错误。50 是一个很好的阈值,因为在这种情况下,电池电量会急剧变化,例如,电池电量为 10 的小型摩托车被拿出来充电,然后电池电量为 100 时再放回去。在这种情况下,电池电量的差值将为 90。这种情况会正确地认识到这种不规则性,并且不会将这种踏板车计入我们的分析中。
工作日(左)和周末(右)出行最多的五种滑板车
4.特征工程和聚类。
Feature 使用上述规则将原始数据集设计成固定/非固定以及工作日/周末组合的子集。
下面是静态踏板车的一些统计数据…
平日的固定滑板车
周末的固定滑板车
正如你从上面所看到的,在某个特定的点上有一系列的滑板车。为了产生洞察力,我们需要了解踏板车的整体运动,而不是关注个人。我们使用 K-Means 和 HDBSCAN 聚类方法来实现这一点。
结果和结论:
我们已经做好了所有的准备工作,现在是公布结果的时候了…
1.滑板车在华盛顿特区的普及程度如何?
当然,踏板车的数量会随着时间的推移而变化,但我在这里的目的只是为了更好地了解 Bird 在该地区部署踏板车的程度。
所以,平均来说,整个华盛顿大约有 5338 辆小型摩托车,这比我最初预计的要多得多。
2.平均行程距离是多少?一周中的哪一天对使用有影响吗?
回答问题的第一部分…平均行程距离约为 1.88 英里
出于对验证我的发现的好奇,我开始在网上搜索任何报告或文章,结果发现,我的发现与网上发表的这篇报告非常接近(下面的链接)**
考虑到这只是基于 1.5 个月的数据,这是非常接近的。
第二部分 …比较不同日期的使用活动…
其中 0 —周一,1 —周二,以此类推。
正如我们从上面看到的,工作日的使用量似乎比周末多一点。
老实说,这让我很惊讶,因为我以为滑板车更多地是用于娱乐目的,因此在周末会有更多的活动。
但是从我上面的结果来看,工作日的活动似乎比周末多。这表明人们可能更多地使用滑板车作为通勤工具。也许上下班的时候。
考虑到华盛顿地区的交通状况,这是有道理的。走出地铁/公交车站后,人们可能会使用滑板车来覆盖他们的“最后一英里”。
当我看到伯德发表的关于其在华盛顿使用滑板车的报告时,我能够验证我的理论
“……50%的骑行者表示他们使用 Bird 上下班。但是乘客们也在使用公共交通工具——29%的乘客使用 Bird 来连接华盛顿地铁和公交车……”——Bird
3.我能从使用模式中学到什么?
有什么比想象更好的理解事物的方法呢?我在 Python 中使用了一个很酷的包,叫做“叶子”,通过它你可以在真实世界的地图上构建自定义的可视化效果。
由于存在大量的滑板车和它们的运动,绘制单个滑板车和它们的运动只会使地图拥挤,难以解释。为了实现这一点,我使用了叶子上很酷的热图和时间插件来绘制热图。你们可能都很了解热图,颜色越深,浓度越高,在这个例子中是指踏板车。**
可视化策略
平日(左)与周末(右)的固定滑板车活动
工作日(左)与周末(右)的非固定踏板车活动
非固定(左),固定(右)周末
4.我能利用上述问题的发现并产生见解吗?
为了做到这一点,我根据地理坐标将踏板车分组,然后通过不同的时间段将这些分组可视化。这将使我对整体动作有一个很好的理解。我使用 K-Means 和 HDBSCAN 聚类算法来实现这一点,然后我比较了不同情况下的结果,如下所示。
平日(左)与周末(右)的固定滑板车活动
工作日(左)与周末(右)的非固定踏板车活动
结论:
- 上面的图像代表了华盛顿的心跳模式。
- 工作日的上午 8 点至 10 点时段显示出较高的使用率,接近中午时段时有所下降,但在下午 4 点至 6 点时段又有所回升。
- 周末的活动比平日少,但使用模式与平日相似。
- 在任何一天,活动在一天的早些时候集中在市中心区域,在一天的晚些时候扩散到市中心周围的区域。代表用户的通勤行为。
来自上面的使用数据和见解可用于找到最常用的路线,并使用该路线,公共交通服务,如公共汽车、地铁等。可以改进。可以对路线进行聚类,然后根据受欢迎程度进行排名。
使用密度可以与基于人口统计的人口密度相互参照,并且资源可以集中于在这两个方面排名靠前的人。
评论或提问?请发邮件至:himanshu.agarwal20792@gmail.com 或联系LinkedIn
分析员工离职调查
使用 python 分析统计严密性和潜在因素以确定员工情绪
图片由来自 Pixabay 的 mohamed Hassan 提供
当分析员工情绪数据时,在我们的例子中是员工离职调查,我们必须考虑四个主题。
- 调查的统计严密性
- 调查对象的人口构成
- 对已定义的潜在结构的总体看法
- 根据受访者的特征(即性别、地点、部门等。)
首先,坚持这种方法将使我们能够确定我们的调查在多大程度上衡量了它想要衡量的东西。其次,从受访者特征的角度了解谁回答了调查(即性别、部门等),我们可以为我们的分析和结果提供背景。第三,这种方法将帮助我们确定响应者的总体情绪。最后但同样重要的是,它将帮助我们不仅确定哪些组织计划可能有助于增加情绪,而且还确定这些计划应该在哪里实施。
资料组
我们将使用的数据集是一个虚构的员工离职调查,该调查向员工提出了一系列关于其组织人口统计数据的问题(即部门)和 5 分李克特(即。强烈不同意,不同意,中立,同意,强烈同意)情绪问题(即。该组织提供了大量的晋升机会。没有使用开放式问题。
数据处理
import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import seaborn as snsimport warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_columns', None)
%matplotlib inlinewith open('exit_data_final.csv') as f:
df = pd.read_csv(f)
f.close()df.info()
我们向员工询问了 33 个项目或问题。在我们开始分析之前,我们需要进行一些数据清理。
df.drop('Unnamed: 0', axis=1, inplace=True)
让我们放弃这个奇怪的“未命名”专栏,因为它没有任何意义。
for var in df.columns:
print(var, df[var].unique())
通过检查每个项目的唯一值,我们可以看到一些问题。
- 有些项目的缺失值被正确地标记为 np.nan,但其他项目则为空。
- 基于 df.info(),我们需要转换 Likert 项目的项目类型,因为它们当前被格式化为“objects”。
- 最后,我们需要转换一些值,以提高可视化效果的可读性。
# Replacing nulls with np.nan
for var in df.columns:
df[var].replace(to_replace=' ', value=np.nan, inplace=True)# Converting feature types
likert_items = df[['promotional_opportunities', 'performance_recognized',
'feedback_offered', 'coaching_offered', 'mgmt_clear_mission',
'mgmt_support_me', 'mgmt_support_team', 'mgmt_clear_comm',
'direct_mgmt_satisfaction', 'job_stimulating', 'initiative_encouraged',
'skill_variety', 'knowledge_variety', 'task_variety', 'fair_salary',
'teamwork', 'team_support', 'team_comm', 'team_culture',
'job_train_satisfaction', 'personal_train_satisfaction', 'org_culture',
'grievances_resolution', 'co-worker_interaction',
'workplace_conditions', 'job_stress', 'work/life_balance']]for col in likert_items:
df[col] = pd.to_numeric(df[col], errors='coerce').astype('float64')# Discretization of tenure
bins = [0,4,9,14,19,24]
labels = ['0-4yrs', '5-9yrs', '10-14yrs', '15-19yrs', '20+yrs']
df['tenure'] = pd.cut(df['tenure'], bins = bins, labels=labels)
潜在结构的发展
在我之前的 文章 中,我们回顾了分析统计严密性的过程(即。效度、信度、因子分析)。请随意回顾,但让我们快速回顾一下什么是潜在调查结构以及它们是如何派生的。
为了开发保持良好的统计严谨性的调查项目或问题,我们必须从学术文献开始。我们想要找到一个理论模型来描述我们想要测量的现象。例如,性格调查经常会使用大五模型(即。开放性、尽责性、外向性、宜人性和神经质)来开发调查项目。调查开发人员将为模型的每个组成部分精心设计 2-10 个(取决于调查的长度)项目。旨在评估相同成分的项目被称为测量“潜在结构”。换句话说,我们没有明确地测量“外向性”,因为这是一个“观察到的结构”,而是通过个别调查项目间接测量。在达到一定的严格程度之前,该调查将使用多个受访者样本进行试点测试。同样,如果你对用于确定严密性的统计分析感兴趣,看看我以前的 文章 。
# Calculating latent variables
df['employee_valued'] = np.nanmean(df[['promotional_opportunities',
'performance_recognized',
'feedback_offered',
'coaching_offered']], axis=1)df['mgmt_sati'] = np.nanmean(df[['mgmt_clear_mission',
'mgmt_support_me', 'mgmt_support_team', 'mgmt_clear_comm', 'direct_mgmt_satisfaction']], axis=1)df['job_satisfaction'] = np.nanmean(df[['job_stimulating',
'initiative_encouraged','skill_variety','knowledge_variety',
'task_variety']], axis=1)df['team_satisfaction'] = np.nanmean(df[['teamwork','team_support',
'team_comm','team_culture']], axis=1)df['training_satisfaction'] = np.nanmean(df[['job_train_satisfaction',
'personal_train_satisfaction']], axis=1)df['org_environment'] = np.nanmean(df[['org_culture','grievances_resolution',
'co-worker_interaction','workplace_conditions']], axis=1)df['work_life_balance'] = np.nanmean(df[['job_stress','work/life_balance']], axis=1)df['overall_sati'] = np.nanmean(df[['promotional_opportunities', 'performance_recognized','feedback_offered', 'coaching_offered', 'mgmt_clear_mission','mgmt_support_me', 'mgmt_support_team', 'mgmt_clear_comm','direct_mgmt_satisfaction', 'job_stimulating', 'initiative_encouraged','skill_variety', 'knowledge_variety', 'task_variety', 'fair_salary','teamwork', 'team_support', 'team_comm', 'team_culture', 'job_train_satisfaction', 'personal_train_satisfaction', 'org_culture', 'grievances_resolution', 'co-worker_interaction', 'workplace_conditions', 'job_stress', 'work/life_balance']], axis=1)
我们的离职调查也已经发展到评估某些潜在的结构。每个调查项目都根据它要测量的潜在因素进行平均。最后,我们计算了一个“总体满意度”特征,该特征计算了每位受访者所有项目/潜在因素的总体平均值。
下面是调查项目的列表以及它们要测量的潜在结构。请记住,为了便于可视化,每个项目的每个标签都被大大缩短了。你可以想象这些问题,比如“从 1 到 5 分,我觉得我的工作很刺激”。
mappings = {1:'1) Dissatisfied', 2:'1) Dissatisfied', 3:'2) Neutral', 4:'3) Satisfied', 5:'3) Satisfied'}
likert = ['promotional_opportunities', 'performance_recognized',
'feedback_offered', 'coaching_offered', 'mgmt_clear_mission',
'mgmt_support_me', 'mgmt_support_team', 'mgmt_clear_comm',
'direct_mgmt_satisfaction', 'job_stimulating', 'initiative_encouraged',
'skill_variety', 'knowledge_variety', 'task_variety', 'fair_salary',
'teamwork', 'team_support', 'team_comm', 'team_culture',
'job_train_satisfaction', 'personal_train_satisfaction', 'org_culture',
'grievances_resolution', 'co-worker_interaction',
'workplace_conditions', 'job_stress', 'work/life_balance']for col in likert:
df[col+'_short'] = df[col].map(mappings)
df.head()
为了有助于可视化,我们将创建新的功能,将 1 和 2 的评分汇总为不满意,3 为中性,4 和 5 为满意。这将使我们能够创建具有 3 个独特部分的堆积条形图。
受访者的特征
了解调查受访者的人口统计数据有助于为我们的分析提供背景信息。我们还必须记住,大多数员工离职调查都是在自愿的基础上完成的。由于这个令人困惑的变量,我们需要将从数据中收集的任何见解作为组织事务的“证据”,而不是确定的“证据”。员工可能对组织非常满意或生气,他们的态度肯定会反映在他们的回答中。最后,该调查包括大约 600 名受访者,我们需要注意不要将这些视为过去 4 年中发生的所有终止的总数。600 例终止可能只占已发生的所有终止中的一小部分。
换句话说,我们的分析希望确定那些对调查做出回应的员工对几个组织因素的满意度。我们不应该将我们的结果推广到更广泛的组织或所有被解雇的员工。
def uni_plots(feature, text):
tmp_count = df[feature].dropna().value_counts().values
tmp_percent = ((df[feature].dropna().value_counts()/len(df))*100).values
df1 = pd.DataFrame({feature: df[feature].value_counts().index,
'Number of Employees': tmp_count,
'Percent of Employees': tmp_percent})
f, ax = plt.subplots(figsize=(20,10))
plt.title(text, fontsize=25, pad=30)
plt.tick_params(axis='both', labelsize=15, pad=10)
plt.xlabel(feature, fontsize=20)
plt.xticks(size=18)
plt.yticks(size=18)
sns.set_color_codes('pastel')
count = sns.barplot(x=feature, y='Number of Employees', color='b', data=df1, label='Number of Employees')
for p in count.patches:
count.annotate(format(p.get_height(), '.1f'),
(p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center',
xytext = (0, 9),
textcoords = 'offset points', size = 20)
sns.set_color_codes('muted')
percent = sns.barplot(x=feature, y='Percent of Employees', color='b', data=df1, label='Percent of Employees')
for i in percent.patches:
percent.annotate(format(i.get_height(), '.1f'),
(i.get_x() + i.get_width() / 2., i.get_height()),
ha = 'center', va = 'center',
xytext = (0, 9), size = 20,
textcoords = 'offset points')
ax.set_ylabel('')
ax.legend(ncol=2, loc="upper right", fontsize=15, frameon=True)
sns.despine(left=False, bottom=False)
ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
plt.show() def bi_cat_plot(feature1, feature2):
ax = pd.crosstab(df[feature1], df[feature2], normalize='index')*100
ax1 = ax.plot(kind='barh', stacked=True, figsize=(25,15), fontsize=25)
for i in ax1.patches:
width, height = i.get_width(), i.get_height()
x, y = i.get_xy()
ax1.text(x+width/2,
y+height/2,
'{:.0f} %'.format(width),
horizontalalignment='center',
verticalalignment='center',
size=25)
plt.title('Percentage of Termination Reasons by {}'.format(feature1), fontsize=30, pad=25)
plt.ylabel(' ')
plt.legend(prop={'size':20})
在 4 年(2017 年至 2020 年)的数据收集中,生产、客户服务和销售部门占调查受访者的 65%以上。在四年的调查中,我们的受访者有很大的差异。2018 年有 342 例终止,而 2020 年只有 37 例。
近 50%的受访者是自愿终止妊娠,这是一个重要的事实。同样,在没有确凿的 HRIS 数据的情况下,很难将我们的结果推广到更广泛的组织中,但终止原因的差异大小向我们表明,该公司可能在自愿终止方面存在问题。我们没有任何员工绩效数据来确定建设性的或令人遗憾的自愿离职,因为这将使我们能够专门集中分析令人遗憾的自愿离职。
受访者中最大的年龄组是 56 岁及以上,几乎占受访者的 20%。按离职原因划分年龄,我们可以看到这个年龄组占退休人数的 87%,这是有道理的。如果我们看看自愿离职和年龄,我们可以看到所有年龄组的平均分布。
类别数量较少的变量(如性别)通常不会为我们提供很多见解,除非我们看到数据存在重大偏差。与男性相比,女性对调查的回应比例似乎接近 2:1。从终止原因的性别来看,我们看到的百分比反映了我们看到的整体性别比例。
根据工作类型,我们的受访者分布相当均匀。我们确实看到管理和行政工作类型占了很小的一部分,但这些职位在整个组织中所占的比例更小。更有趣的是,我们看到高管的非自愿离职激增(38%)。与其他终止原因相比,我们通常会看到非自愿终止的情绪较低,因此,我们可以预计高管的整体情绪得分相当低。
总的来说,我们看到自愿离职的受访者占大多数,他们主要来自生产、客户服务和销售部门,并在职业生涯的后期(41 岁及以上)离职。
总体受访者情绪
通过首先取所有单个情感项目的平均值(即李克特项目)。最后,根据数据的切片方式计算出一个总平均值。
def overall_plot(feature):
ax = round(df.groupby(feature)['overall_sati'].mean(),2).sort_values().plot(kind='barh', stacked=True,
figsize=(25,15), fontsize=25)
for i in ax.patches:
width, height = i.get_width(), i.get_height()
x, y = i.get_xy()
ax.text(x+width/2,
y+height/2,
'{:.2f}'.format(width),
horizontalalignment='center',
verticalalignment='center',
size=25)
plt.title('Overall Employee Sentiment by {}'.format(feature), fontsize=30, pad=25)
plt.ylabel(' ')
总的来说,人力资源的总体情绪最低,但在受访者中所占比例也最小(5.8%)。这是有问题的,因为平均水平会很快被少数对李克特项目评价很低的受访者左右。也就是说,我们不能忽视这种整体平均情绪的低迷。另一方面,生产和销售是受访者中最大的两个部分,他们得分第二和第三低。我们必须了解这三个部门,以确定哪些因素得分特别低。
非自愿离职在总体情绪中得分最低,这一事实并不令人惊讶。被公司解雇通常会产生负面情绪,这会影响你对调查的反应。更有趣的是,自愿终止妊娠是回复率的第一大原因,在整体情绪中排名第二。了解自愿离职的潜在原因可以为留住高绩效员工带来巨大的好处。
我们可以看到 46-50 岁年龄组得分最低。
不出所料,高管在总体情绪上得分特别低,因为他们中有 38%的人是被非自愿解雇的,但我们也必须记住,有 33%的人是被自愿解雇的。我们肯定要调查高管在哪些具体因素上得分特别低。最后,machine_ops 在总体情绪上得分同样较低(3.39),该群体的自愿终止回应率也为 52%。
最后,我们看不出终身职位群体的总体情绪有任何特别的差异。
总之,人力资源、生产和销售的总体情绪最低,人力资源得分(3.39)。由于生产和销售部门的受访者人数最多,我们需要检查这些部门在哪些具体因素上得分最低。非自愿离职的情绪最低(3.42),这并不奇怪,但自愿离职的平均情绪第二低,同时拥有最多的调查受访者。最后,我们看到高管和机器操作工作类型的情绪最低(3.39)。然而,还需要额外的分析来确定这种关系的性质。
平均整体潜在因素情绪
emp_value_avg = round(np.mean(df['employee_valued']),2)
mgmt_sati_avg = round(np.mean(df['mgmt_sati']),2)
job_sati_avg = round(np.mean(df['job_satisfaction']),2)
team_sati_avg = round(np.mean(df['team_satisfaction']),2)
training_sati_avg = round(np.mean(df['training_satisfaction']),2)
org_env_avg = round(np.mean(df['org_environment']),2)
work_life_avg = round(np.mean(df['work_life_balance']),2)
overall_sati = round(np.mean([emp_value_avg, mgmt_sati_avg, job_sati_avg, team_sati_avg,
training_sati_avg, org_env_avg, work_life_avg]), 2)
temp_dict = {'emp_value_avg': emp_value_avg, 'mgmt_sati_avg': mgmt_sati_avg,
'job_sati_avg': job_sati_avg, 'team_sati_avg': team_sati_avg,
'training_sati_avg': training_sati_avg, 'org_env_avg': org_env_avg,
'work_life_avg': work_life_avg, 'overall_sati': overall_sati}
tmp_df = pd.DataFrame.from_dict(temp_dict, orient='index', columns=['average']).sort_values(by='average')plt.figure(figsize=(25,15))
plt.title('Overall Latent Factor Averages', fontsize=28)
plt.ylabel('Average Employee Rating', fontsize=25)
ax = tmp_df['average'].plot(kind='barh', fontsize=25)
for i in ax.patches:
width, height = i.get_width(), i.get_height()
x, y = i.get_xy()
ax.text(x+width/2,
y+height/2,
'{:.2f}'.format(width),
horizontalalignment='center',
verticalalignment='center',
size=25)plt.grid(False)
plt.show()
如果我们忽略任何受访者的人口统计数据,并查看我们的潜在情绪因素,我们会看到 fair_salary、emp_value 和 org_env 得分最低。将我们的分析集中在这些因素上是很重要的,以便理解这些因素为什么低,以及它们在组织中的最低位置(即部门、工作类型等。).我们的结果也被确认为自愿终止妊娠。
likert = ['promotional_opportunities', 'performance_recognized',
'feedback_offered', 'coaching_offered', 'mgmt_clear_mission',
'mgmt_support_me', 'mgmt_support_team', 'mgmt_clear_comm',
'direct_mgmt_satisfaction', 'job_stimulating', 'initiative_encouraged',
'skill_variety', 'knowledge_variety', 'task_variety', 'fair_salary',
'teamwork', 'team_support', 'team_comm', 'team_culture',
'job_train_satisfaction', 'personal_train_satisfaction', 'org_culture',
'grievances_resolution', 'co-worker_interaction',
'workplace_conditions', 'job_stress', 'work/life_balance'] likert_avgs = []
for col in likert:
likert_avgs.append(round(np.nanmean(df[col]),2)) likert_avgs_df = pd.DataFrame(list(zip(likert, likert_avgs)), columns=['likert_item', 'avg_sentiment'])
likert_avgs_df.set_index('likert_item', inplace=True) plt.figure()
ax = likert_avgs_df.plot(kind='bar', figsize=(25,15), fontsize=20)
for i in ax.patches:
width, height = i.get_width(), i.get_height()
x, y = i.get_xy()
ax.text(x+width-0.25,
y+height+.1,
'{:.2f}'.format(height),
horizontalalignment='center',
verticalalignment='center',
size=20)
plt.title('Average Overall Likert Item Sentiment', fontsize=30, pad=25)
plt.legend().remove()
plt.xlabel(' ')
通过检查构成每个潜在因素的 Likert 项目,我们可以看到晋升机会和绩效认可对员工价值的低情绪贡献最大。虽然我们希望看到不止一个 Likert 项目来评估工资满意度,但我们可以更详细地检查 fair_salary,以确定这种情绪在哪里特别低。最后,组织文化和不满解决似乎是导致组织环境情绪低落的最主要原因。
被调查者特征潜在构念情感
当分析调查数据时,很容易陷入众所周知的图表和绘图的兔子洞,却看不到你的目标。换句话说,我们需要缩小关注范围。分析情绪调查的最终目标是识别薄弱环节,在这些环节中可以实施组织计划来改进这些已识别的环节。我们主要关注的领域是自愿终止妊娠。首先,他们几乎占受访者的 50%。第二,这是我们可以对使用组织计划产生最大影响的员工群体。最后,我们希望限制自愿离职的数量,以限制组织的知识流失,并最大限度地减少与雇用替代员工相关的招聘和培训成本。
# plotting average likert sentiment by respondent characteristics for voluntary terminations
def bi_volterm_plot(feature1, feature2):
tmp_df = df.loc[(df['reason_of_term']=='vol_term')]
ax = round(tmp_df.groupby(feature1)[feature2].mean(),2).sort_values().plot(kind='barh', stacked=True,
figsize=(25,15), fontsize=25)
for i in ax.patches:
width, height = i.get_width(), i.get_height()
x, y = i.get_xy()
ax.text(x+width/2,
y+height/2,
'{:.2f}'.format(width),
horizontalalignment='center',
verticalalignment='center',
size=25)
plt.title('Average {} Sentiment of Voluntary Terminations by {}'.format(feature2, feature1),fontsize=30, pad=25)
plt.ylabel(' ')
plt.legend(prop={'size':20})
员工价值因素
毫不奇怪,人力资源部门的自愿离职在 emp_value 上得分最低,因为人力资源部门的整体情绪最低。此外,采购、客户服务和生产得分也相对较低。这一点很重要,因为生产和客户服务部门拥有最多的调查对象。
因为我们知道晋升机会和公认的绩效是低员工价值情绪背后的主要驱动因素,所以我们根据部门、年龄和工作类型绘制了这些李克特项目。
仅过滤自愿离职的数据,我们可以看到大多数部门在晋升机会方面得分较低,但客户服务、人力资源和采购得分特别低。年龄得分特别低,因为 21-50 岁之间自愿离职的员工在晋升机会情绪方面得分非常低。46-50 岁年龄组在这个李克特项目上得分很低(2.62)。主动离职的高管、管理人员、机器操作人员和服务人员工作类型在晋升机会情绪方面得分较低,高管得分非常低(2.17)。最后,除了 1-14 岁,所有终身职位组在晋升机会上得分较低,但 5-9 岁得分尤其低(2.80)。
我们再次看到采购和人力资源在公认绩效方面得分最低,但 IT 和生产部门的满意度也相当低。从员工年龄的角度来看,26-50 岁的得分最低,而且这些年龄组在晋升机会方面得分也很低。我们再次看到行政、管理和机器操作工作类型得分最低。最后,就像我们看到的晋升机会一样,5-9 岁的任期组在公认绩效方面得分最低。
公平工资因素
Fair_salary 在受访者的总体情绪中得分第二低。现在我们已经过滤了自愿离职的数据,让我们看看哪里的薪资情绪最低。不出所料,人力资源和采购部门得分最低。然而,倾向于在许多李克特项目上得分较低的产品实际上得分相对较高。也就是说,我们确实看到 R&D 和 IT 在这个李克特项目上得分较低。46-50 岁的人再次得分较低,他们的平均情绪得分为(2.76)。主管、管理和行政工作类型在 fair_salary 中得分最低。最后,5-9 岁的任期组得分最低。
组织环境因素
我们知道,就整体情绪而言,组织环境因素排名倒数第三。当我们过滤自愿离职时,我们看到人力资源部和采购部在这一因素上得分最低。让我们把这个因素分解成各个李克特项目。
我们从上面的分析中看到,组织文化和不满解决是李克特的两个项目,这两个项目似乎对组织环境的低整体情绪贡献最大。
看起来组织文化在所有部门的得分都很低,有时非常低。从年龄的角度来看,46-50 岁的人的 org_culture 最低,但 26-50 岁的人的 org _ culture 普遍较低。高管在李克特项目上得分极低(1.80),其次是机器操作和管理。也就是说,除了销售,所有工作类型的平均得分都低于 3.00。最后,从任期的角度来看,所有任期组的得分都低于 3.00,5-9 岁组的得分特别低。
采购、人力资源、客户服务和生产部门再次在申诉解决方面得分最低。在年龄组中,我们再次看到 46-50 岁的受访者情绪最低落。高管和机器操作人员对解决不满的情绪最低。最后,5-9 年任期组在李克特项目上得分最低,非常像组织文化。
概括起来
受访者的特征
自愿终止妊娠构成了最大的调查对象群体,接近 50%。这是个好消息,因为它让我们有足够的数据来洞察一个特别重要的群体的组织情绪。了解自愿离职发生的潜在原因可以帮助公司减少人员流动,从而最大限度地减少知识流失和招聘成本。
从部门角度来看,生产(32%)、客户服务(19%)和销售(16%)构成了大部分调查反馈。尽管其余部门的回复率明显较低,但他们的回复数量并没有低到足以证明他们的见解毫无意义。
56 岁及以上的受访者构成了最大的群体(23%),其余群体相对均衡,20 岁及以下的受访者仅占 2.3%。通过按终止原因对这些数据进行切片,我们可以看到 56 岁及以上的应答者中有 54%正在退休。
女性回答调查的比例几乎是男性的 2:1。
工作类型在调查中表现得相当均衡,服务人员(19%)、专业人员(17%)和销售人员(14%)位居前三。高管回答调查的比例最低(3%),只有 24 人回答。24 是一个相对较低的响应数,可能会产生不准确的结果。此外,行政工作类型的非自愿离职比例最大(38%),我们知道非自愿离职倾向于产生更负面的整体情绪。任何关于高管群体的见解都必须经过更大样本量的仔细审查和验证。
最后,组织任期产生平均 20%每个任期组响应的平均表示。
整体员工情绪
正如预期的那样,非自愿终止的平均情绪最低,但紧随其后的是自愿终止。似乎人力资源、生产和销售部门的整体情绪最低。此外,年龄在 46-50 岁之间以及行政、机器操作和管理工作类型的受访者整体情绪最低。
总体情绪最低的三个潜在因素是薪酬满意度(3.14)、员工价值(3.31)和组织环境(3.46)。如果我们检查这三个潜在因素的 Likert 项目/问题,我们可以看到,晋升机会和绩效认可对员工价值的影响最小。公平薪酬对薪酬满意度的影响最低。最后,组织文化和不满解决对组织环境的总体情绪最低。
自愿离职情绪
当具体观察自愿终止妊娠的人群时,我们看到了与上述类似的结果。薪资满意度、员工价值和组织环境的整体情绪最低。晋升机会和绩效认可再次成为员工价值情绪低落的驱动因素。公平工资是低工资满意度的主要驱动力。最后,组织文化和不满解决在组织环境潜在因素上得分最低。
晋升机会(员工价值)
大多数部门都需要改善晋升机会,但采购、人力资源、客户服务和生产部门最需要阻止自愿离职。此外,在大多数年龄组,尤其是 46-50 岁的人群中,晋升机会情绪似乎很低。行政人员、管理人员、机器操作人员和服务人员工作类型最缺乏晋升机会。最后,5-9 岁的终身雇员对晋升机会的情绪最低。
绩效认可(员工价值)
采购和人力资源部门受到了低绩效认可的困扰。46-50 岁的人、行政人员、管理人员和机器操作人员的工作类型对绩效认可的情绪也很低。最后,我们再次看到 5-9 年的任期似乎对这一项目的情绪很低。
公平工资(工资满意度)
人力资源、采购、R&D 和 IT 部门对他们的薪水的看法最低。年龄较大的群体,尤其是 46-50 岁的人,情绪低落。高管、管理人员和行政人员的薪酬满意度得分最低。
组织文化(组织环境)
在整个调查中,似乎组织文化的整体情绪最低。所有部门,尤其是采购、客户服务和生产部门,似乎都受到情绪低落的困扰。年龄和工作类型也是如此,46-50 岁的人和行政人员、机器操作人员和管理人员似乎得分最低。
申诉解决(组织环境)
该主题的情绪通常高于组织文化,但它仍然是导致组织环境情绪低落的主要因素。同样的部门,采购、人力资源、客户服务和生产得分最低。随着 46-50 岁的人再次得分最低,这一趋势还在继续。管理人员、机器操作人员和服务人员得分也最低。
行动纲要
我们将注意力集中在分析一项员工离职调查上,该调查是在 4 年前从大约 600 名员工中收集的。绝大多数调查应答者(50%)是自愿终止妊娠的。生产、客户服务和销售部门构成了大多数受访者。整体受访者的年龄略有倾斜,因为最大的群体是 56 岁及以上。最后,工作类型按比例表示。
非自愿终止妊娠的总体情绪最低,紧随其后的是自愿终止妊娠。总体而言,薪酬满意度、员工价值和组织环境的情绪最低。这些结果也在自愿终止的样本中得到证实。
然后分析特别集中在自愿终止的样品上。似乎为了提高员工的整体情绪并潜在地减少自愿离职,组织需要将其计划集中在改善晋升机会、绩效认可、薪酬、组织文化和申诉解决上。
任何旨在改善上述领域以潜在阻止自愿离职的举措最好针对采购、人力资源、客户服务和生产部门(根据需要)。此外,年龄在 46-50 岁、26-30 岁和 36-40 岁之间的员工也将从任何和所有计划中受益。从工作类型的角度来看,管理人员、机器操作人员、管理人员和服务人员也将从这些计划中受益(按需求排序)。值得注意的是,高管工作类型的样本量很小(24),其中只有三分之一是自愿离职。总体而言,该调查的样本量相对较小(600 人),因此,建议通过更大的调查样本来验证这些结果。也就是说,这些结果确实提供了对可能存在于内部的组织问题的一点了解。
NBA 交易截止后的球迷情绪分析
通过推特上关于截止日期前交易的信息来了解球队的整体球迷情绪。
介绍
今年我们看到了一些最大和最令人惊讶的季中交易。随着一些明显的赢家和其他人对其选择的怀疑,这显然是一个复杂情绪的管弦乐队。然而,在我解释我写这篇文章的原因之前,我想给非 NBA 球迷一些一周前发生的事情的背景。
随着 2020 年 2 月 6 日 NBA 19-20 赛季交易截止日期的临近,球迷们急切地等待着“Woj 炸弹”的降临,而谣言则通过各种人员在不同的平台上传播开来。关于这些谣言有太多要说的了,因为至少其中一些以某种形式成为了事实。
这真的在 twitter 上引起了爆发,我很享受看着推文滚动,试图抓住每个粉丝的情绪。
球员和媒体的反应很容易推断,但当你有 30 支不同球队的这么多球迷时,你如何确切地分辨谁感到不安,谁没有?一些球队因为失去了一名优秀的球员却没有得到太多的回报而明显感到沮丧,而另一些球队可能会以较小的数量表达他们的不满。
https://commons . wikimedia . org/wiki/File:2014 _ Toronto _ 猛龙 _fans_Air_Canada_Centre.jpg
所以,我做了一点挖掘,并恢复了过去 10 天中受交易截止日期影响最大的球队的推文。我的目标是了解这些球队的整体球迷情绪,并看看他们如何相互竞争。我将分析的 7 个团队是:
- 亚特兰大老鹰队
- 克利夫兰骑士队
- 洛杉矶快船队
- 迈阿密热火队
- 休斯顿火箭队
- 明尼苏达森林狼队
- 金州勇士队
所以,事不宜迟,让我们开始吧。
谢尔盖·库图佐夫在 Unsplash 上拍摄的照片
该过程
我在 Python (Jupyter)上做了完整的分析,如果你想复制这个过程,我将为你添加我的代码片段。
需要以下库:NLTK、Textblob、Pandas(只需 pip 安装)。
您希望从导入以下内容开始:
import pandas as pd
import nltk
nltk.download('stopwords')
import en_core_web_sm
from textblob import TextBlob
import seaborn as sb
from bs4 import BeautifulSoup
我将每个团队的所有推文保存为单独的 CSV 文件,所以我上传了以下内容:
HawksTweets = pd.read_csv("HawksTweets.csv")
CavaliersTweets = pd.read_csv("CavaliersTweets.csv")
ClippersTweets = pd.)read_csv("ClippersTweets.csv")
HeatTweets = pd.read_csv("HeatTweets.csv")
TimberwolvesTweets = pd.read_csv("TimberwolvesTweets.csv")
RocketsTweets = pd.read_csv("RocketsTweets.csv")
WarriorsTweets = pd.read_csv("WarriorsTweets.csv")
然后,我会将它们合并到一个数据集,如下所示:
teams = [HawksTweets, CavaliersTweets, ClippersTweets, HeatTweets, TimberwolvesTweets, RocketsTweets, WarriorsTweets]
TeamTweets = pd.concat(teams)
因为我们只想分析英语的推文:
TeamTweets = TeamTweets[TeamTweets['Language'] == 'English']
现在数据集已经准备好使用 NLTK 了。
文本预处理
首先,我们希望将文本标记成单独的单词,这样我们就可以删除停用词、非英语单词和无意义的标签。我们用下面一行来标记:
TeamTweets['tokenized_sent'] = TeamTweets.apply(lambda row: nltk.word_tokenize(row['Text']), axis=1)
现在我们用漂亮的汤清洗课文:
soup = BeautifulSoup(TeamTweets['tokenized_sent'])
text = soup.get_text(strip=True)
TeamTweets['tokenized_sent'] = text
接下来,我将定义一个函数来分解文本,并进行一些额外的清理,这可能有助于我们完成这个过程。我还使用来自 NLTK 的英语单词语料库去除了非英语单词。
def untokenize(words):
text = ' '.join(words)
step1 = text.replace("`` ", '"').replace(" ''", '"').replace('. . .', '...')
step2 = step1.replace(" ( ", " (").replace(" ) ", ") ")
step3 = re.sub(r' ([.,:;?!%]+)([ \'"`])', r"\1\2", step2)
step4 = re.sub(r' ([.,:;?!%]+)$', r"\1", step3)
step5 = step4.replace(" '", "'").replace(" n't", "n't").replace(
"can not", "cannot")
step6 = step5.replace(" ` ", " '")
return step6.strip()englang = words = set(nltk.corpus.words.words())
TeamTweets['Sentences'] = TeamTweets['tokenized_sent'].apply(lambda x: [item for item in x if item in englang])
TeamTweets['Sentences'] = TeamTweets['Sentences'].apply(lambda x: untokenize(x))
现在,我有了一个包含已处理文本的数据集,它已经准备好进行 TextBlob 的情感分析。
https://commons . wikimedia . org/wiki/File:2019 _ NBA _ 总决赛 _ 游戏 _2.jpg
情感分析
首先,让我们为分数插入一列:
TeamTweets.insert(2, "Score", "", True)
接下来,我将定义一个函数,它将为我们的数据帧中的每一行文本提供情感输出。我将对我们的行应用这个函数,并获得每条推文的情感分数。
def senti(x):
return TextBlob(x).sentimentTeamTweets['Score'] = TeamTweets['Sentences'].apply(senti)
我们以主观性和极性的形式得到分数。您可以使用下面的方法用 seaborn 来绘制它们:
ax1 = sb.catplot(x="Subjectivity", y="Team", data=df)
我的 Jupyter 笔记本截图
ax2 = sb.catplot(x="Polarity", y="Team", data=df)
我的 Jupyter 笔记本截图
现在,让我们通过对项目求和来计算每个团队的总得分。这将给出我们的最终得分,这将是球迷情绪的一个指标。
首先,我创建一个新的数据框,每个团队作为一行。
key = {'Team': ['Atlanta Hawks', 'Cleveland Cavaliers', 'LA Clippers', 'Miami Heat', 'Minnesota Timberwolves', 'Houston Rockets', 'Golden State Warriors']}
keydata = pd.DataFrame(key)
将团队列为一个列表:
teamlist = keydata.Team.tolist()
list2 = keydata.indexteams = []
teams.extend([list(a) for a in zip(teamlist, list2)])
最后,我编写了一个函数,根据球队名称计算得分总和:
def add_team(team_tuple):
score = df.loc[(df['Team'] == team_tuple[0]),'Polarity'].sum()
keydata.loc[keydata.index[team_tuple[1]]] = score
returnfor team in teams:
add_team(team)
最终的数据框汇总了每个队的得分。根据他们的推文,得分越高,这支球队的球迷情绪就越积极。
我的 Jupyter 笔记本截图
用图表展示我们的最终结果:
ax3 = sb.barplot(x="Score", y="Team", data=keydata)
我的 Jupyter 笔记本截图
结论
我们可以看到骑士队在得到安德烈·德拉蒙德后情绪不佳(考虑到他的合同还有 3 个月到期),所以他们只拥有他很短一段时间。另一方面,迈阿密热火队在交易了安德烈·伊戈达拉后,似乎得到了球迷们最好的反应。
埃德加·恰帕罗在 Unsplash 上拍摄的照片
感谢你的阅读!希望你学到了新东西。
更多类似内容关注我!