TowardsDataScience 博客中文翻译 2020(二百二十四)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

Python 中的理解和生成器表达式

原文:https://towardsdatascience.com/comprehensions-and-generator-expression-in-python-2ae01c48fc50?source=collection_archive---------23-----------------------

Python 理解能力的综合指南。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

要理解 Python 的理解能力,首先理解理解的概念很重要。编程中的理解只不过是以简洁明了的方式编写(现有的)代码,通常只有一行。它正在通过缩短现有序列来构建新序列。擅长理解代码是一项相当重要的技能。你需要理解实现来应用编程的理解能力。Python-3 支持理解,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python 3: Image by author 支持的理解类型

在本文中,我们将深入探讨这一点。让我们首先修改一下用 Python 创建列表的方法。

在 Python 中定义列表的不同方法

  1. 直接列出元素,
    一个列表可以通过枚举/列出方括号中的所有元素来直接定义,用逗号分隔。
Output:
-------
num_cube[] = [8, 64, 216, 512, 1000]

*f " num _ cube[]= { num _ cube } "*print()中使用的是 Python 3.6 中引入的字符串插值f-string 格式样式。

2.使用 for 循环,
定义一个空列表,并使用内置方法 append()for 循环中添加元素。在这里,我们创建了一个从 1 到 10 的偶数立方体列表。我们已经为循环定义了一个空列表 num_cube_floop 和一个来迭代范围(1,11)* 。条件 n%2==0 将确保输出表达式 n**3 只计算偶数。*

Output:
-------
num_cube_floop[] = [8, 64, 216, 512, 1000]

3.使用’ += '运算符,
类似于上面的例子我们已经定义了一个空列表 num_cube_op 和*,因为*循环正在迭代输入文件 list_input.txt 。在使用操作符 ‘+=’ 将输入数据添加到列表之前,我们使用 split( ) 分离输入数据。

Output:
-------
num_cube_op[] = ['8', '64', '216', '512', '1000']

4.使用 map()
在这里,我们使用 map() 从现有的列表*中创建一个列表。*对于刚接触 map()lambda 表达式的人来说,

map(function_to_apply,iterable)——它将‘function’应用于一个‘iterable’的所有元素。

lambda 参数:输出表达式 —匿名函数,它是在没有名称的情况下定义的,并且不遵循正常的 python 函数约定。

在下面的代码中, map( )lambda 表达式(计算数字的立方)应用于现有列表数字,并在将结果分配给新列表 num_cube_maps 之前使用 list( ) 转换输出。

Output:
-------
num_cube_map[] = [8, 64, 216, 512, 1000]

还有另一种方法可以使用 Python 的理解能力来创建列表。它让我们用一行代码创建一个列表。一开始可能看起来有点难以理解,因为语法可能令人生畏,但在这里我们将分解它以获得更简单的理解。

列表理解

List Comprehension 的一般语法类似于 集合生成器符号 。在集合论中,“集合构造符号是一种数学符号,它通过列出集合的所有元素或描述所有集合元素必须满足的性质来定义集合。”

这个定义的第二部分。根据集合的性质定义集合 在集合论中也叫集合理解。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

“|”的左边是一个输出表达式,右边是一个集合中所有元素都必须满足的规则。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

集合论中的集合生成器符号:作者的图像

这里,输出表达式(n3)定义了集合的元素,规则由输入集合( n ϵ N )和过滤条件(n < =10 和 n!=0).上面的集合定义基本上映射了集合 N (0,1,2,…)中的所有自然数。)并用 n < =10 和 n 限制输入!=0.结果集是,

S1={8,64,216,512,1000}
S1 = { n3 ∣ n ϵ N,n < =10 和 n!=0}

类似地,列表理解的最基本语法是,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

让我们直接进入例子,

  • 同 for 循环
Output:
-------
name_ltr[] = ['P', 'y', 't', 'h', 'o', 'n', ' ', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'm', 'i', 'n', 'g']With List-Comprehension = ['P', 'y', 't', 'h', 'o', 'n', ' ', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'm', 'i', 'n', 'g']

在上面的例子中,我们需要创建一个字符串的所有字符的列表。实现这一点的通常方法是定义一个空列表,并在一个 for 循环中使用 append( ) 方法将元素添加到我们的列表中。这里 for 循环正在迭代字符串文字 name 并且 append( ) 将字符串文字的所有单个字符添加到列表 name_ltr 中。使用列表理解可以获得相同的结果,甚至不用担心定义列表或使用 append()。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

如果我们检查语法,列表理解只不过是对循环的和输出表达式的重新排列。我们把输出表达式放在理解语法的开头,后面跟着循环。这很简单。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

  • 用 if 语句

让我们进一步向它添加一个条件语句。在文章的开始,我们看到了创建偶数立方列表的四种不同方法,一种是使用进行循环。

Output:
-------
num_cube_floop[] = [8, 64, 216, 512, 1000]With List-Comprehension = [8, 64, 216, 512, 1000]

正如我们在代码块中看到的,使用 List Comprehension 时,四行代码减少到了一行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

语法类似于我们在上一节中讨论的基本格式,条件性的【T2 if】-语句被添加到末尾,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

让我们再举一个例子,从一个给定的字符串中的一个特定字母开始创建一个单词列表。

Output:
-------
words[] = ['Python', 'Programming']With List-Comprehension = ['Python', 'Programming']

在这里, statement.split( ) 分隔(默认分隔符是空格)字符串文字中的所有单词,并且循环的将遍历它以过滤从字母‘P’开始的所有单词,结果将使用 words.append(w) 添加到列表中。使用 List Comprehension 时,输出表达式(总是放在开头) w 放在最前面, for 循环迭代字符串文字,后面是 if- 条件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

  • 带 if-else 语句

if-else 语句的位置与之前讨论的列表理解场景不同。当使用 if-else 条件块时,它被放置在输出表达式之后,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

让我们创建一个数字列表<10 where we have to calculate square for even numbers and cube for the odd.

Output:
-------
square_cube[] = [0, 1, 4, 27, 16, 125, 36, 343, 64, 729]With List-Conprehension = [0, 1, 4, 27, 16, 125, 36, 343, 64, 729]

The above code block filters the numbers based on condition num%2==0 ,并相应地执行*square _ cube . append(num * * 2)*或 square_cube.append(num**3)。使用时,列出与相关联的理解输出表达式,如果-语句放在前面,其他条件后面跟有 else 关键字。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

如何将常见的 for 循环转换成列表理解,这完全取决于我们。在另一个奇数/偶数的例子中,我在一个列表数字中创建了两个列表,代码块是不言自明的。

Output:
-------
numbers[] = [([0, 4, 16, 36, 64], [1, 27, 125, 343, 729])]With List-Conprehension = [[0, 4, 16, 36, 64], [1, 27, 125, 343, 729]]

区别在于如何使用理解来构造列表,为了在一个列表中创建两个列表,定义了两个单独的表达式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

  • 带有嵌套循环

在总结清单理解之前,让我们再举一个例子。它非常包容,大多数时候可以灵活地用于嵌套循环。我们需要创建两个现有列表 char_list 和 *int_list 的笛卡尔积的列表。用于循环的一个char_list 上迭代,另一个在 *int_list 上迭代。使用 cartesian.append((x,y)) 将两个列表中的元素作为元组添加到结果列表 cartesian 中。

Output:
-------
cartesian[] = 
 [('a', 0), ('a', 1), ('a', 2), ('a', 3), ('b', 0), ('b', 1), ('b', 2), ('b', 3), ('c', 0), ('c', 1), ('c', 2), ('c', 3)]With List-Conprehension = 
 [('a', 0), ('a', 1), ('a', 2), ('a', 3), ('b', 0), ('b', 1), ('b', 2), ('b', 3), ('c', 0), ('c', 1), ('c', 2), ('c', 3)]

输出表达式是一个元组,因此要加上括号。在列表理解中,嵌套的 for 循环按照预期的执行顺序放置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

在下面的代码块中, person 是由两个字典组成的列表。他们每个人都有两把钥匙,名字语言。现在我们必须创建一个与键语言相关的列表。第一个 for 循环是遍历一个列表 person 的长度,第二个 for 循环被设置为获取给定索引处的键、语言的值。

Output:
-------
person[] = ['Python', 'Java', 'C++', 'C#']With List-Conprehension = ['Python', 'Java', 'C++', 'C#']

通过在一行中重新排列循环的*,使用列表理解可以获得相同的结果。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

集合理解

集合理解的功能与列表理解的功能相同,只是它返回一个集合,并且使用花括号{ }代替方括号[ ]。当它返回一个集合时,它将具有唯一的元素(Set 属性)。

考虑我们之前讨论过的同一个例子,一个由三个字典条目组成的列表 person 。如果我们在代码块中对比 List 和 Set Comprehension 的语法,区别只是方括号和花括号。虽然输出不同,但与列表理解不同,集合理解返回唯一的值。

Output:
-------
With List-Conprehension = ['Python', 'Java', 'C++', 'Python', 'Python']With Set-Conprehension = {'Python', 'C++', 'Java'}

词典理解

与列表和集合理解不同,当数据预期为键值配对格式时,使用字典理解。让我们继续我们的数字立方体例子的遗产。

Output:
-------

cubes = {1: 1, 2: 8, 3: 27, 4: 64, 5: 125, 6: 216, 7: 343, 8: 512, 9: 729, 10: 1000} 
With Dictionary-Comprehension = {1: 1, 2: 8, 3: 27, 4: 64, 5: 125, 6: 216, 7: 343, 8: 512, 9: 729, 10: 1000}

使用我们前面讨论过的列表理解创建了两个列表。

numbers=[num for num in range(1,11)]  
#numbers=[1,2,3,4,5,6,7,8,9,10] num_cube=[n**3 for n in numbers]
#num_cube=[1,8,27,64,125,216,343,512,729,1000]

我们需要创建一个字典,其中一个的值是来自列表数字的数字,而来自第二个列表 num_cubes。for 循环在 zip(numbers,num_cube) 上迭代,以获取元组形式的键和值(key,value)。

zip(iterables) —映射多个容器的相似索引,并返回一个迭代器,该迭代器是一系列元组,包含参数中传递的每个 iterable 的元素。

print(list(zip(numbers,num_cube)))Output:
-------
[(1, 1), (2, 8), (3, 27), (4, 64), (5, 125), (6, 216), (7, 343), (8, 512), (9, 729), (10, 1000)]

输出表达式 *key:value,*映射从 zip(numbers,num_cube) 返回的值,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

生成器表达式

生成器表达式的语法类似于列表理解,只是它使用括号( )而不是方括号[ ]。生成器是 Python 中的特殊迭代器,它返回生成器对象。使用它的目的是生成一个项目序列,而不必将它们存储在内存中,这就是为什么你只能使用一次生成器。

这里,我们使用列表理解创建了一个列表 num_cube_lc ,生成器表达式定义为 num_cube_generator

Output:
-------
List Comprehension = [8, 64, 216, 512, 1000]Generator Comprehension = <generator object <genexpr> at 0x00000238925E7848>Sum = 1800

生成器表达式的输出不像列表理解的输出;但是,当与 sum( ) 一起使用时,它会传递从表达式中生成的值。

结论

一般来说,理解肯定是减少代码行的有效方法。代码大部分时间更容易阅读和理解;然而,如果你的程序逻辑中有多个嵌套循环,使用理解会牺牲可读性。尽管有些人认为理解是一种更 pythonic 化的编码方式,但真正决定使用或避免它的最佳情况还是由您自己来决定。该主题的一些有用资源是,

本文使用的代码可以从我的 GitHub 库中获得。

Kaggle &泰坦尼克号生存预测竞赛综合初学者指南

原文:https://towardsdatascience.com/comprehensive-beginners-guide-to-kaggle-titanic-survival-prediction-competition-solution-21c5be2cec2c?source=collection_archive---------23-----------------------

我对 Kaggle 上泰坦尼克号生存预测比赛的解答与分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

安妮·斯普拉特Unsplash 上拍摄

如果你认识我,我是 Kaggle 的超级粉丝。天知道我在之前的文章中在 Medium 上提到过 Kaggle 多少次。

但是我在卡格尔的旅程并不总是充满玫瑰和阳光,尤其是在开始的时候。由于缺乏经验,我最初很难理解机器学习项目背后的工作流程以及所使用的不同术语。

经过几个月的网上资源筛选,包括许多技术文章、文档和教程视频,我才慢慢开始学习标签编码、交叉验证和超参数调整等概念。

我当时希望有一个一站式商店,在那里我不仅可以学习机器学习项目背后的步骤,更重要的是学习这些步骤背后的原理。所以我想,为什么不利用我在过去几个月里积累的知识来创建一个一站式商店,以帮助那些可能和我经历同样事情的人呢?

在本文中,我将解释什么是机器学习问题,以及端到端机器学习项目背后的步骤,从导入和读取数据集到参考 Kaggle 上最受欢迎的初学者比赛之一,即泰坦尼克号生存预测比赛,建立预测模型。

这个项目将我引入了机器学习的世界,也是我在 Kaggle 上的第一次比赛。

什么是机器学习?

实际上,在我们进入机器学习之前,我认为重要的是,我们首先要理解建立模型背后的目的。

什么是模型,为什么它很重要?

模型是对现实世界的模拟和简化。

无论你是建造摩天大楼的工程师,还是挑选股票的对冲基金分析师,甚至是正在竞选的政治家,我们都要应对这个世界的不确定性。一个模型可以让我们最小化这种不确定性,做出更明智的决策。

现在,一个模型并不完美,它不会帮助我们持续预测一个特定事件的实际结果,但我们肯定可以非常接近它。有了大量的数据,再加上人工智能的力量,人类已经越来越擅长使用模型进行高质量、准确的预测。

这就是机器学习的用武之地。

机器学习是指计算机学会推导数据中存在的趋势和模式。机器学习模型是使用历史数据“训练”的。一旦模型被建立和测试,我们就可以用它来预测其他数据点的未来。

机器学习模型不同于统计模型,因为它们需要最少的人力,但更重要的是,机器学习模型比统计模型需要更少的假设。统计模型是数学密集型的,并且基于系数估计。它要求建模者理解不同变量之间的关系,然后才能将它们包含在模型中。

由于上述原因,机器学习模型不仅做出更准确的预测,而且做出更稳健的预测,远远超过任何普通模型的能力。

监督与非监督学习

为了让这篇文章对初学者尽可能全面,我还想强调机器学习的两个主要分支,即监督学习和非监督学习。理解这两个分支之间的区别将有助于我们更仔细地思考我们试图使用机器学习来解决的问题类型。

监督学习是使用标记数据训练模型。换句话说,我们明确地告诉我们的模型我们预测的样本结果。

监督学习可以进一步分为分类和回归。分类问题是当我们的预测的结果是离散的和绝对的。另一方面,回归具有连续分布的预测。

为了说明分类问题,假设您正在构建一个垃圾邮件分类器,帮助您将电子邮件分类为垃圾邮件或非垃圾邮件。你要做的是准备一个训练集,其中包含你的电子邮件的不同特征,如字数,特定单词和标点符号的使用,最重要的是它们的标签,垃圾邮件或非垃圾邮件。然后,您可以使用该训练集训练机器学习模型,该模型将开始学习特征及其标签之间的关系。一旦模型建立到你想要的精度,你可以使用这个分类器来分类你的其他邮件。

回归与分类的不同之处在于,回归问题的结果是连续的,而不是有一个离散的目标变量,如垃圾邮件或非垃圾邮件。回归的一个完美例子是预测房价。在这里,房价是一个连续变量。同样,我们需要准备一个训练集,其中包含房屋观察以及它们的特征,如卧室数量、离城市的距离、客厅面积,当然还有它们的销售价格。然后,模型将了解不同的房屋特征如何与最终销售价格相关联。随后,我们可以使用这个模型对其他房屋进行预测。

现在,让我们转到无监督学习。

正如我们在垃圾邮件分类器和房价预测示例中看到的那样,我们将样本结果(垃圾邮件与非垃圾邮件以及最终销售价格)作为训练集的一部分,以训练我们的机器学习模型。

在无监督学习中,我们不包括任何样本结果,而是简单地让模型导出数据中存在的任何潜在模式,并相应地对它们进行分组。

由于大多数 Kaggle 比赛都使用监督学习,所以我不会在本文中过多详细地讨论无监督学习。

本次比赛的目的

这个比赛的目的是建立一个机器学习模型,帮助我们预测泰坦尼克号上乘客的生存结果。

这是监督学习中二元分类问题的一个例子,因为我们将乘客的结果分为两类,泰坦尼克号幸存者或幸存者。

更具体地说,我们想调查不同的乘客特征,如他们的年龄、性别、机票等级等如何影响他们的生存结果。

这个竞争的评估标准是测试集中被我们的分类器正确预测的乘客的百分比。

笔记本漫游

在这一节中,我将简要讨论我分析竞争的步骤和结果。你可以在我的 GitHub 上找到这个项目的完整代码。

数据集

Kaggle 竞赛中有三种类型的数据集。在更高级的比赛中,您通常会发现更多也更复杂的数据集,但一般来说,它们属于三类数据集之一。

  1. **训练集:**这是我们将执行大部分数据操作和分析的数据集。关于泰坦尼克号生存预测比赛,我们希望分析和/或创造能够帮助我们预测乘客生存结果的特征。此外,顾名思义,这是我们将用来训练我们的机器学习模型的数据集。
  2. **测试集:**一旦我们的分类器建立起来,我们希望确保它不仅适合我们的训练数据,更重要的是能够对样本外数据进行预测。回想一下,建模的主要目的是预测未用于训练我们的模型的真实世界数据。如果模型仅在训练数据上表现良好,而无法对新数据进行预测,则该模型本质上是无用的。Kaggle 将使用测试集来评估我们模型的准确性,即我们的分类器在对我们的分类器以前没有见过的乘客数据进行分类时有多准确。测试集比训练集少一列,训练集是响应(目标)变量,在我们的例子中,是幸存的一列。
  3. **样本提交:**这是我们向 Kaggle 提交最终解决方案的格式。我们最终的数据框架需要与样本提交数据框架具有相同的形状(相同的行数和列数)以及相同的列标题。

数据描述

在这里,我将概述 dataset 中列的定义。您可以在竞赛页面的数据选项卡下找到这些信息。

  • 幸存: 0 =没有幸存,1 =幸存
  • Pclass: 客票等级,其中 1 =头等,2 =二等,3 =三等
  • 性别:男或女
  • **年龄:**以年为单位的年龄
  • 泰坦尼克号上兄弟姐妹或配偶的数量
  • Parch: 泰坦尼克号上父母或子女的数量
  • **车票:**旅客车票号码
  • **票价:**客运票价
  • **客舱:**客舱号
  • 登船:登船地点,C =瑟堡,Q =皇后镇,S =南安普敦

探索性数据分析

探索性数据分析是可视化和分析数据以获取洞察力的过程。我们将主要使用熊猫图书馆来完成这项任务。我有大量关于熊猫的教程,你可以在这里查阅。

在这一部分,我将讨论我的 EDA 的关键结果。同样,你可以在我的笔记本上找到完整的分析。

我首先将数据集中的所有特征分为分类变量和数字变量,并对它们进行单独分析,以了解它们与存活率之间的关系。

执行特征分析时,区分分类变量和数值变量非常重要,因为这有助于我们更恰当地构建分析。例如,我们不能计算性别等分类变量的平均值,因为平均值只能应用于具有连续值分布的数值变量。

训练集中的分类变量是性别、p 类别和上船。另一方面,数字变量包括 SibSp、Parch、Age 和 Fare。

以下是我从 EDA 过程中收集的一些见解:

  • 女乘客生还的可能性远远大于男乘客。如果我没记错的话,在泰坦尼克号的疏散过程中,妇女和儿童是优先考虑的,所以女性比男性有更高的生还机会是有道理的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

女性乘客比男性乘客更有可能生还

  • 头等舱乘客比二等舱乘客更有可能生还,二等舱乘客比三等舱乘客更有可能生还。头等舱乘客是社会地位、影响力和财富都很高的个人。如果他们在疏散过程中比其他乘客优先,我不会感到惊讶。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

头等舱乘客的生存概率最高;三等舱的乘客生还概率最低

  • 费用是与存活率最(正)相关的数字特征。乘客支付的机票越多,他/她生还的可能性就越大。有道理!参考上面关于头等舱乘客的圆点点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 0.26 处,费用与存活率最相关

  • 年龄较小的乘客,尤其是儿童,比其他乘客有更高的生存概率。再次强调,疏散时“妇女和儿童优先”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

年龄较小的乘客尤其是儿童更有可能幸存

数据预处理

数据预处理是为模型训练准备好训练集的过程。在这里,我们处理缺失值,应用数据转换,进行特征工程以及标签编码。

我现在将简要地介绍一下这些步骤,但我强烈建议您参考我的笔记本,以便更好地理解这里正在讨论的内容。

  • **缺失值:**有两种处理缺失值的方法,丢弃或者填充。用替代值填充缺失值有时称为插补。每种方法在方便性和准确性之间都有自己的权衡。具体地说,从我们的数据框架中删除缺失值是处理缺失数据的最简单和最方便的方法。然而,这通常是以丢失数据集中潜在的有用信息为代价的。另一方面,插补需要更多的时间和考虑才能有效执行。因此,您应该考虑数据集中缺失数据的状态,以便决定最合适的处理方法。根据经验,如果某个列或特性严重缺失,我们可以安全地将其从数据集中删除。关于泰坦尼克号的比赛,我决定去掉客舱特性,而填充上船、票价和年龄特性。
  • **数据转换:**该步骤特定于数据集中的 Fare 列。票价分布具有很高的正偏度,即分布向左侧倾斜。偏斜是指数值集中在分布的一端。这可能会对我们的模型做出准确预测的能力产生不利影响。因此,通过数据转换来解决这个问题至关重要。更具体地说,我对 Fare 列应用了一个对数变换,这导致偏斜度从 4.51 急剧下降到 0.57。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对数转换前的乘客票价

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对数转换后的客运票价

  • **特征工程:**特征工程是从现有特征中创建新特征的过程,以更好地向预测模型表示潜在问题。这可以说是机器学习中最重要的艺术。之所以称之为艺术,是因为特征工程通常伴随着经验和领域专业知识,即特定问题或行业的知识。在我的笔记本上,我创建了三个新的功能,它们是标题,孤独和年龄。
  • **标签编码:**机器学习模型要求所有的输入输出变量都是数值。因此,我们需要在将分类数据拟合到我们的模型之前,对它们进行编码。

模特培训

比赛最精彩的部分来了,模特!

如果你正在用 Python 语言编码,Scikit-learn 是最流行的机器学习库之一。他们也有一个关于工具的全面的文档,这些工具包含在用于数据预处理、建模、模型评估和超参数调整的库中。

在我们可以将训练集拟合到我们的模型之前,我们需要首先将训练集分成预测变量和响应变量。

我已经选择将训练集适合于十个不同的分类器,它们是:

  1. 逻辑回归
  2. 支持向量分类器
  3. k-最近邻
  4. 高斯朴素贝叶斯
  5. 感知器
  6. 线性随机向量分类器
  7. 随机梯度下降
  8. 决策图表
  9. 随机森林
  10. CatBoost

在 Scikit-learn 中建模需要三个简单的步骤。首先,我们需要实例化我们的模型,也就是简单地声明一个模型并将它赋给一个变量。接下来,我们需要使模型符合我们的训练集,包括预测变量和响应变量。最后,我们可以使用这个模型对测试集进行预测。

模型评估

模型评估中的一个重要概念是交叉验证。回想一下,一个只适合训练数据,但无法对新数据进行预测的模型基本上是没有用的。交叉验证为我们测试模型预测新数据的准确性提供了一种方法。

交叉验证是这样一个过程:我们只使用训练集的一个子集来重复训练我们的模型,剩余的数据留待以后测试。保留的数据有时称为维持集。

交叉验证提供了比仅仅训练准确性更准确的模型准确性评估。这是因为训练精度忽略了过拟合的问题。过度拟合是指我们的模型学习数据中的噪声而不是信号。坚持集背后的想法是,我们的模型可以根据它对未经训练的数据进行预测的能力进行评估。

在我的笔记本中,我选择了其他模型中的支持向量分类器,因为它具有最高的交叉验证均值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

支持向量分类器具有最高的交叉验证均值

超参数调谐

超参数调整是调整模型参数的过程。使用 GridSearchCV,我设法调整了我的支持向量分类器的参数,并看到模型精度略有提高。

决赛成绩

使用我新调好的支持向量分类器,我对测试集进行了预测,并在提交给 Kaggle 时获得了 0.77511 的提交分数。换句话说,我成功预测了测试集中 77.5%的乘客数据。

提高模型准确性的可能扩展

我几个月前做了这个项目,我还没有机会修改它,提高我的模型精度。然而,这里我有几个可能的扩展,您可以潜在地添加到您的项目中,使它比我的更好:

  • 分析机票和客舱列,而不是删除它们
  • 在功能工程中提出比我现有的功能更好的替代功能
  • 删除不太重要的特征以减少过度拟合
  • 尝试集成建模,它结合了各种机器学习分类器的结果

结论

泰坦尼克号生存预测比赛是机器学习中分类问题的一个例子。

在这个项目中,我们分析了泰坦尼克号上乘客的不同特征,并随后建立了一个机器学习模型,可以将这些乘客的结果分为幸存或未幸存。

在将十个不同的分类器拟合到我的训练数据之后,支持向量分类器显示出最有希望和最准确的预测结果。因此,我选择了这个分类器作为我的选择模型,并获得了 0.77511 的提交分数,也就是说,我正确预测了测试集中 77.5%的乘客数据。

我希望这篇文章和我的笔记本可以帮助你绕过我刚开始学习机器学习时的最初驼峰,更重要的是,激励你在未来参加更多的 Kaggle 比赛。

你可以在我的 GitHub 这里找到完整的笔记本。如果你有任何问题,请随时联系我。

快乐学习!

视频教程

如果你喜欢通过视频学习,我的 YouTube 频道上有两个视频,详细介绍了这个项目。如果你感兴趣,一定要去看看!

第 1 部分:探索性数据分析

第 2 部分:数据预处理和建模

全面的流失预测和分析

原文:https://towardsdatascience.com/comprehensive-churn-prediction-and-analysis-d552e0e56162?source=collection_archive---------26-----------------------

我们的模型能准确检测客户流失以帮助留住这些客户吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由安德烈·亨特Unsplash 上拍摄

客户流失,也称为流失,发生在客户停止与公司做生意的时候。理解和发现客户流失是留住这些客户和改进公司产品的第一步。

电信数据集

我们将在电信公司-客户-流失数据集上训练我们的流失模型,以预测客户离开虚拟电信公司 Telco 的可能性。这个合成数据集是由 IBM 整理的,包括一个标签,指示客户是否在上个月离开。

**目标:**根据人口统计和服务信息预测客户是否会流失。

数据探索

探索和建模将使用 Jupyter 笔记本进行。我们首先加载所需的库,并将数据作为 DataFrame 导入。

索引第一行向我们展示了数据集中的列和一个样本记录。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

检查数据集

这个数据集相当干净,没有遗漏条目。总共有 7043 个观察值。大约有 27%的客户表示不满(这对电信公司来说不是一个好兆头)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以使用 pandas 的info(), describe()方法快速查看数据完整性。数据集没有任何缺失值。虽然熊猫把SeniorCitizen解读为连续变量,但实际上是一个二元指标。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

连续变量

我们可以使用 seaborn 库来进一步可视化和检查数据集。让我们先来考察连续变量tenure, MonthlyCharges。由于TotalCharges可以近似为这两个变量的函数,我们将把它从图中排除。

Seaborn 的 pairplot 功能绘制数据中的成对关系。

解释配对图:

  • 对角线轴是特定变量的直方图,y 轴测量出现的次数
  • 除了轴被翻转之外,图的左下方三角形和右上方三角形捕捉相同的信息

直方图告诉我们,流失客户的任期分布是右偏的,而流失客户的任期分布更加均匀。它还显示,拥有更高MonthlyCharges的客户会看到更高的流失率。

其他两个图表捕捉到非常相似的信息,橙色的“流失”和蓝色的“未流失”之间有明显的区别。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我们使用箱线图来说明流失客户和未流失客户之间的分位数差异。

第一个方框图比较了两组之间的任期分位数。流失客户的平均任期比未流失客户短得多。75%最终离开电信公司的客户都是在头 30 个月内离开的。在电信公司工作了 70 个月后,出现了一些异常情况。

第二个方框图比较了月度费用和流失率。流失客户的月平均费用明显高于没有流失的客户。这表明折扣和促销可能是吸引顾客留下来的一个原因。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

人口统计变量

接下来,我们可以检查分类变量,从人口统计变量开始:性别、老年人、伴侣和家属。

  • 左上图统计了性别和流失之间的交集。男性和女性客户之间的流失率差异很小。
  • SeniorCitizen(右上图)、没有Partners(左下图)和没有dependents(右下图)的客户中,流失率较高。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二个发现可能会引起业务涉众的兴趣。没有伴侣和家属的非老年人描述了一个特殊的顾客群体。与其他指标不同的是,这一人群中较低的人员流动频率对他们的终身价值有积极意义。

其他分类变量

其他分类变量可以在服务和计费信息之间划分。

这些图表捕获了服务变量。没有在线安全、在线备份、设备保护和技术支持的客户流失比例更高。基于我对电信服务相当有限的了解,我认为大多数人不会经常为这些服务付费。这表明那些对电信服务越来越信赖的人,或者那些需要他们的设备用于特殊用途的人,倾向于减少流失。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我们研究计费方法和客户流失之间的交集。左上方的图表比较了不同合同类型的客户流失情况。不足为奇的是,那些计划越短(逐月)的人流失率越高。那些有长期计划的人在提前取消时会面临额外的障碍。

令人惊讶的是,那些选择不使用纸质账单的人和那些使用电子支票支付的人一样更频繁地流失。其中一些行为可能会与其他变量混淆(例如,老年客户可能更喜欢纸质账单)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

训练预测模型

这里是我们将要用来构建模型的库。我正在使用一个新的笔记本,所以我将再次加载数据。

TotalCharges列中,11 条记录是一个空字符串。所有这些记录都有一个有效的MonthlyCharges值,但是期限为 0。我们可以用 0 来估算这些值。

为建模准备数据

首先,我们对分类变量进行热编码,并在训练和测试之间随机划分数据。在划分数据集之后,除了评估目的,我们不接触测试集。

特征选择

先前的探索已经给出了许多关于哪些特性是重要的信息。我们可以结合卡方检验来做出更明智的决定。

**注意:**卡方检验只能用于评估分类变量。

我们需要理解 p 值来解释卡方输出。

如果零假设为真,并且我们多次重复相同的实验,P 值表示更多“极端”观察的百分比。这里的零假设是该特征对目标没有影响(即,我们不应该将其用作预测器)。低 p 值表明我们应该拒绝我们的零假设——换句话说,拒绝特征没有效果的说法。

大多数要素的 p 值都非常低。只有gender, MultipleLines, PhoneService的 p 值>为 0.05,这意味着我们没有足够的证据来否定这些特征没有影响的说法。这与图表中的故事非常吻合。

为了更直观,让我们用 p 值> 0.05 来绘制所有分类变量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们还将删除TotalCharges,因为同样的信息被MonthlyCharges, Tenure捕获,以进一步减少维度(其他两列似乎比这一列更可靠,这一列有数据问题)。

模特培训

我们将使用逻辑回归模型来预测流失。这可能不一定是性能最好的算法或分离数据最好的算法(找出答案的唯一方法是尝试),但让我们假设它对于简化来说足够好。逻辑回归的另一个优点是易于解释。

我们可以利用 Sklearn 的GridSearchCV函数进行超参数调谐。。由于我们不想在最后才触及我们的测试集,我们将进一步把我们的训练集划分为训练和验证。

我们将使用网格搜索来确定这些超参数的最佳值:—我们是否应该使用 L1 或 L2 罚值—C 值—我们是否应该拟合截距类权重(我们是否应该更加强调流失类而不是非流失类)。

在幕后,GridSearchCV 将根据验证集训练和评估众多模型,以确定哪些超参数产生最佳性能。

**注意:**虽然我在这里没有这么做,但是GridSearchCV可以用来测试候选算法。

我们可以使用grid.best_params_找到最佳超参数值,在本例中为:

{'C': 1, 'fit_intercept': False, 'penalty': 'l1'}

有了这些信息,我们就可以正式训练我们的模型了。我们将在训练集和验证集上训练模型。

评估模型

让我们用我们的测试集来测试这个模型。分类报告给出了精度、召回率和 f1 值(支持是每一类中的观察数量)。微观 f1 分数为 0.81,而宏观 f1 分数为 0.74。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以用混淆矩阵来形象化这个结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于一个初始模型来说还不错。这个合成数据集非常简单:真实世界的电信公司可能会有超过 7000 个数据点。其他变量,如客户与支持人员的互动,可能有助于预测流失。

然而,这个数据集令人难以置信地干净,并带有一个我们在现实世界中经常找不到的良好的流失指示器。

感谢您的阅读!

如果你喜欢这篇文章,可以看看我关于数据科学、数学和编程的其他文章。通过 Medium 关注我的最新动态。😃

作为一个业余爱好项目,我还在www.dscrashcourse.com建立了一套全面的免费数据科学课程和练习题。

如果你想支持我的写作,下次你报名参加 Coursera 课程时,可以考虑使用我的会员链接。完全公开—我从每一次注册中获得佣金,但不会对您产生额外费用。

再次感谢您的阅读!📕

使用 Matplotlib 实现全面的数据可视化

原文:https://towardsdatascience.com/comprehensive-data-explorations-with-matplotlib-a388be12a355?source=collection_archive---------21-----------------------

深入研究电影镜头数据集

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由弗兰基·查马基Unsplash 上拍摄

介绍

数据可视化是数据科学家工具包中的基本技能之一。有了正确的数据,拥有讲述令人信服的数据故事的能力可以为任何组织打开一个机会的金矿,为他们服务的任何人创造价值——别忘了让员工更有效率。

过去,我写过一些实现有效数据可视化的技巧,然而,在那篇文章中,我没有使用单一数据集来探索所有被分享的想法。因此,在这篇文章中,我们将使用我分享的技巧进行一些可视化,并深入研究 MovieLens 数据集。

[## 有效的数据可视化

编辑描述

towardsdatascience.com](/effective-data-visualization-ef30ae560961)

要获得这篇文章中使用的全部代码,请访问我的 Github 库。

[## kurtispykes/推荐系统

permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…

github.com](https://github.com/kurtispykes/recommender_system/blob/master/notebooks/01_kpy_explorations.ipynb)

数据

如前所述,我们将使用 MovieLens 数据集。具体来说,我们将使用 MovieLens 100K 电影评级数据集,该数据集由 1700 部电影的 1000 名用户组成。这些数据是通过 MovieLens 网站在 1997 年 9 月 19 日至 1998 年 4 月 22 日的 7 个月期间收集的。该数据已被清理—评分少于 20 或没有完整人口统计信息的用户已从该数据集中删除。

[## MovieLens 100K 数据集

MovieLens 100K 电影分级。稳定的基准数据集。1000 个用户对 1700 部电影的 100,000 次评分。已发布…

grouplens.org](https://grouplens.org/datasets/movielens/100k/)

为了有效地执行我们的可视化,我们关注收集的 3 个特定数据集:

  • u.data —包含完整的数据集,943 名用户对 1682 个项目的 100000 个评分。
  • u.item —项目信息(电影)
  • u.user —用户的人口统计信息

在这个项目中,我使用了流行的数据科学库,如 Pandas 用于数据操作,Matplotlib 用于数据可视化,NumPy 用于处理数组。此外,我将 Python 的 datetime 模块用于一般的日历相关函数,将 IPython 用于交互式计算。

我们首先简单地导入框架并使用 Pandas read_csv加载数据——参见文档

**import** numpy as np
**import** pandas as pd
**import** matplotlib.pyplot as plt 

**from** datetime **import** datetime
**from** IPython.display **import** IFrame

**import** warnings 
warnings.filterwarnings("ignore")# read data
rating_df= pd.read_csv("../data/u.data", sep="\t", names=["user_id", "item_id", "rating", "timestamp"])

item_df = pd.read_csv("../data/u.item", sep="|",encoding="latin-1", 
                      names=["movie_id", "movie_title", "release_date", "video_release_date",
                             "imbd_url", "unknown", "action", "adventure", "animation",
                             "childrens", "comedy", "crime", "documentary", "drama", "fantasy", 
                             "film_noir", "horror", "musical", "mystery", "romance", 
                             "sci-fi", "thriller", "war", "western"])

user_df = pd.read_csv("../data/u.user", sep="|", encoding="latin-1", names=["user_id", "age", "gender",
                                                                            "occupation", "zip_code"])

取提供给我们的 3 个数据帧:u.datau.itemu.user,我们将它们转换成熊猫数据帧并存储在变量中,如下所示:

  • rating_df —保存用户给出的所有评级的完整 u 数据集
  • item_df —物品信息(电影)
  • user_df —关于用户的人口统计信息

交叉核对数据

我袖手旁观的一般经验法则是总是检查我被告知我被给予的正是已经被提供的。Pandas 通过df.info()df.head()(或df.tail())函数使识别这些事情变得容易,这些函数给我们提供了关于数据帧的更多信息,并允许我们看到数据的预览。

首先,我先看一下rating_df,我们预计会有 943 位用户对 1682 件商品给出 100000 个评价。

# peak at ratings_df
**print**(rating_df.info())
rating_df.head()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 4 columns):
 #   Column     Non-Null Count   Dtype
---  ------     --------------   -----
 0   user_id    100000 non-null  int64
 1   item_id    100000 non-null  int64
 2   rating     100000 non-null  int64
 3   timestamp  100000 non-null  int64
dtypes: int64(4)
memory usage: 3.1 MB
None

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到我们有 100000 个评级,但我们希望确保有 943 个用户和 1682 个项目。

# checking unique users
**print**(f"# of Unique Users: {rating_df['user_id'].nunique()}")# checking number of items
**print**(f"# of items: {rating_df['item_id'].nunique()}")# of Unique Users: 943
# of items: 1682

很好。我们可以确认rating_df确实拥有它所说的东西。然而,经过进一步检查,我注意到我们有一个时间戳变量,但它目前显示为一个int64数据类型。从自述文件中,我发现这个数据帧的时间戳列是从 1970 年 1 月 1 日开始的 unix 秒。因此,我们使用Datetime(一个 Python 内置的)将timestamp列的 Dtype 转换为datetime64

# convert timestamp column to time stamp 
rating_df["timestamp"] = rating_df.timestamp.apply(lambda x: datetime.fromtimestamp(x / 1e3))

# check if change has been applied 
**print**(rating_df.info())
rating_df.head()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 4 columns):
 #   Column     Non-Null Count   Dtype         
---  ------     --------------   -----         
 0   user_id    100000 non-null  int64         
 1   item_id    100000 non-null  int64         
 2   rating     100000 non-null  int64         
 3   timestamp  100000 non-null  datetime64[ns]
dtypes: datetime64[ns](1), int64(3)
memory usage: 3.1 MB
None

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您现在可以在终端打印输出中看到timestamp列现在是数据类型datetime64[ns]

现在我对rating_df已经很舒服了,我可以继续探索item_df了,我们希望它能给我们更多关于这部电影的信息。

# peak at items_df 
**print**(item_df.info())
item_df.head()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1682 entries, 0 to 1681
Data columns (total 24 columns):
 #   Column              Non-Null Count  Dtype  
---  ------              --------------  -----  
 0   movie_id            1682 non-null   int64  
 1   movie_title         1682 non-null   object 
 2   release_date        1681 non-null   object 
 3   video_release_date  0 non-null      float64
 4   imbd_url            1679 non-null   object 
 5   unknown             1682 non-null   int64  
 6   action              1682 non-null   int64  
 7   adventure           1682 non-null   int64  
 8   animation           1682 non-null   int64  
 9   childrens           1682 non-null   int64  
 10  comedy              1682 non-null   int64  
 11  crime               1682 non-null   int64  
 12  documentary         1682 non-null   int64  
 13  drama               1682 non-null   int64  
 14  fantasy             1682 non-null   int64  
 15  film_noir           1682 non-null   int64  
 16  horror              1682 non-null   int64  
 17  musical             1682 non-null   int64  
 18  mystery             1682 non-null   int64  
 19  romance             1682 non-null   int64  
 20  sci-fi              1682 non-null   int64  
 21  thriller            1682 non-null   int64  
 22  war                 1682 non-null   int64  
 23  western             1682 non-null   int64  
dtypes: float64(1), int64(20), object(3)
memory usage: 315.5+ KB
None

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们已经知道从rating_df开始我们的数据中有 1682 个唯一的条目,所以看到movie_idmovie_title列中有 1682 个非空条目让我不寒而栗。然而,video_release_date是完全空的,这意味着它没有为我们提供任何关于电影的信息,这意味着我们可以删除这个专栏。

我注意到release_dateimbd_url也丢失了一些值,但不足以让我们删除该列——如果最坏的情况发生,我们可以通过访问 IMBD 网站并使用电影标题找到imbd_urlrelease_date来手动估算这些值。

我的另一个所谓的“习惯”是,当我在读取数据时,考虑什么样的数据类型是期望的。我期望release_datedatetime64的数据类型,但是经过检查,它的数据类型是 object,所以我按照必要的处理步骤将一个对象转换为 datetime。

# drop empty column 
item_df.drop("video_release_date", axis=1, inplace= True)

# convert non-null values to datetime in release_date
item_df["release_date"] = item_df[item_df.release_date.notna()]["release_date"].apply(lambda x: datetime.strptime(x, "%d-%b-%Y"))

# check if change is applied
print(item_df.info(), item_df.shape)
item_df.head()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1682 entries, 0 to 1681
Data columns (total 23 columns):
 #   Column        Non-Null Count  Dtype         
---  ------        --------------  -----         
 0   movie_id      1682 non-null   int64         
 1   movie_title   1682 non-null   object        
 2   release_date  1681 non-null   datetime64[ns]
 3   imbd_url      1679 non-null   object        
 4   unknown       1682 non-null   int64         
 5   action        1682 non-null   int64         
 6   adventure     1682 non-null   int64         
 7   animation     1682 non-null   int64         
 8   childrens     1682 non-null   int64         
 9   comedy        1682 non-null   int64         
 10  crime         1682 non-null   int64         
 11  documentary   1682 non-null   int64         
 12  drama         1682 non-null   int64         
 13  fantasy       1682 non-null   int64         
 14  film_noir     1682 non-null   int64         
 15  horror        1682 non-null   int64         
 16  musical       1682 non-null   int64         
 17  mystery       1682 non-null   int64         
 18  romance       1682 non-null   int64         
 19  sci-fi        1682 non-null   int64         
 20  thriller      1682 non-null   int64         
 21  war           1682 non-null   int64         
 22  western       1682 non-null   int64         
dtypes: datetime64[ns](1), int64(20), object(2)
memory usage: 302.4+ KB
None (1682, 23)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的处理步骤之后,我们可以看到不再有video_release_date列,并且release_date现在显示为datetime64数据类型。

因为我们被提供了一些 URL,我认为利用这一点并使用来自Ipython库的IFrame查看imbd_url中的一些 URL 可能会很酷。

注意:在imbd_url列中的 URL 可能会永久移动到一个新地址,或者在实施时关闭。此外,当我手动输入电影的 url 时,我无法连接到 IMBD 网页(即,我在 IFrame 中手动输入 copycat (1995) url,它返回拒绝连接-我还没有找到解决方法,但一旦找到,我会更新笔记本。与此同时,我只是简单地使用了 IMBD 主页的 url 来说明它是如何工作的——本质上,我们可以从笔记本上完全访问该网页。

# viewing random imbd_urls
IFrame("https://www.imdb.com", width=800, height=400)

[## IMDb:收视率,评论,以及在哪里看最好的电影和电视节目

IMDb 是世界上最受欢迎和最权威的电影、电视和名人内容来源。查找评分和评论…

www.imdb.com](https://www.imdb.com)

最后但同样重要的是,我们有user_df。如果您没记错的话,这是关于用户的人口统计信息,因此我预计会有 943 行,因为(特别是在user_id列中)我们已经确认数据中有 943 个唯一用户。

# peak at user data
print(user_df.info())
user_df.head()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 943 entries, 0 to 942
Data columns (total 5 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   user_id     943 non-null    int64 
 1   age         943 non-null    int64 
 2   gender      943 non-null    object
 3   occupation  943 non-null    object
 4   zip_code    943 non-null    object
dtypes: int64(2), object(3)
memory usage: 37.0+ KB
None

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很好,我们可以确认我们有 943 个用户:总之,我们有 943 个用户对 1682 部电影的 10 万个评分。为了使笔记本中不同点的数据可视化简单,我决定将我们拥有的数据帧组合在一起——我将在 PyTrix 系列中讨论如何在组合数据中更好地实现这一点。

[## 熊猫:组合数据

编辑描述

towardsdatascience.com](/pandas-combining-data-b190d793b626)

# store full dataframe 
full_df = pd.merge(user_df, rating_df, how="left", on="user_id")
full_df = pd.merge(full_df, item_df, how="left", right_on="movie_id", left_on="item_id")
full_df.head()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

太棒了。我们已经成功地确认了预期的数据正是我们所拥有的。这些信息足以让我们更深入地研究数据并获得更好的理解。

提问,用数据回答

遵循有效数据可视化的协议,我的下一步是思考一些问题,让我更深入地了解手头的数据,然后确定可视化问题答案的最佳方法——最佳方法可以定义为表达问题答案的最简单明了的方式。

注:在这一部分,当我查询数据时,我的想法通常会跳跃。因此,在浏览数据时,我更喜欢使用 Jupyter 笔记本。

收视率最高的 10 部电影有哪些?

# return number of rows associated to each title
top_ten_movies = full_df.groupby("movie_title").size().sort_values(ascending=False)[:10]

# plot the counts
plt.figure(figsize=(12, 5))
plt.barh(y= top_ten_movies.index,
         width= top_ten_movies.values)
plt.title("10 Most Rated Movies in the Data", fontsize=16)
plt.ylabel("Moive", fontsize=14)
plt.xlabel("Count", fontsize=14)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的数据集中,星球大战(1977) 是评分最高的电影。这些信息非常有价值,因为我们可以决定使用我们数据集中评级最高的电影来推荐给新用户,以克服 冷启动问题 *。*我们可以从这个问题中进一步研究我们的数据,并开始思考哪些类型的电影与评分最高的电影相关联——在这种情况下,我们只查看了与《星球大战》相关联的类型。

genres= ["unknown", "action", "adventure", "animation", "childrens", "comedy", "crime", "documentary", "drama", "fantasy", "film_noir", "horror", "musical", "mystery", "romance", "sci-fi", "thriller", "war", "western"]

full_df[full_df.movie_title == "Star Wars (1977)"][genres].iloc[0].sort_values(ascending=False)action         1
sci-fi         1
romance        1
adventure      1
war            1
western        0
documentary    0
animation      0
childrens      0
comedy         0
crime          0
fantasy        0
drama          0
film_noir      0
horror         0
musical        0
mystery        0
thriller       0
unknown        0
Name: 204, dtype: int64

我不是《星球大战》的主要粉丝,尽管我已经看过很多部,但我提到这一点只是为了确认将动作、科幻、冒险、战争和浪漫等类型联系起来对这部电影来说是正确的。

这个问题完全忽略了评分最低的电影,但是如果我们正在构建一个推荐系统,我们不能忽略评分较低的电影,因为可能有许多原因导致电影没有得到很多评分。让我们来看看数据集中评分最低的几部电影。

# the least rated movies 
least_10_movies = full_df.groupby("movie_title").size().sort_values(ascending=False)[-10:]
least_10_moviesmovie_title
Coldblooded (1995)                            1
MURDER and murder (1996)                      1
Big Bang Theory, The (1994)                   1
Mad Dog Time (1996)                           1
Mamma Roma (1962)                             1
Man from Down Under, The (1943)               1
Marlene Dietrich: Shadow and Light (1996)     1
Mat' i syn (1997)                             1
Mille bolle blu (1993)                        1
Á köldum klaka (Cold Fever) (1994)            1
dtype: int64

《生活大爆炸》对我来说是一个惊喜,除了我不熟悉其他电影——反正我不是一个电影人,所以这并不意味着什么。

一个用户评价电影的最大/最小数量是多少?

从数据集提供的自述文件中,我们被告知单个用户评级的电影的最小数量是 20,但是我们不知道单个用户评级的电影的最大数量。

movies_rated = rating_df.groupby("user_id").size().sort_values(ascending=False)
print(f"Max movies rated by one user: {max(movies_rated)}\nMin movies rated by one user: {min(movies_rated)}")Max movies rated by one user: 737
Min movies rated by one user: 20rating_df.user_id.value_counts().plot.box(figsize=(12, 5))
plt.title("Number of Movies rated by a Single user", fontsize=16)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在数据集中,单个用户评级的电影的最大数量是 737 部——不管是谁,都是非常忠诚的电影观众和评级者——而某人评级的电影的中间数量是 70 部。有很多离群值已经评级超过 320 部电影,这是我从上面的情节中近似得出的极值。

每年发行多少部电影?

# create the year column from Movie title 
full_df["year"] = full_df["movie_title"].str.extract("\((\d{4})\)", expand=True)

# return number of rows by the year 
year_counts = full_df[["movie_title", "year"]].groupby("year").size()

fig, ax = plt.subplots(figsize=(12, 5)) 
ax.plot(year_counts.index, year_counts.values)
ax.xaxis.set_major_locator(plt.MaxNLocator(9)) # changes the number of xticks we see
plt.title("Number of movies per Annum", fontsize=16)
plt.xlabel("Year", fontsize= 14)
plt.ylabel("# of Movies Released", fontsize=14)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很难忽略 1988-1998 年间的大幅上涨和下跌。值得做一些研究,并向领域专家提问,以确定在此期间可能会发生什么。

有多少男人/女人评价的电影?

# count the number of male and female raters
gender_counts = user_df.gender.value_counts()

# plot the counts 
plt.figure(figsize=(12, 5))
plt.bar(x= gender_counts.index[0], height=gender_counts.values[0], color="blue")
plt.bar(x= gender_counts.index[1], height=gender_counts.values[1], color="orange")
plt.title("Number of Male and Female Participants", fontsize=16)
plt.xlabel("Gender", fontsize=14)
plt.ylabel("Counts", fontsize=14)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个样本中,男性明显比女性多得多,这可能会对所看电影的类型产生重大影响。

最受男性和女性欢迎的电影类型是什么?

full_df[genres+["gender"]].groupby("gender").sum().T.plot(kind="barh", figsize=(12,5), color=["orange", "blue"])
plt.xlabel("Counts",fontsize=14)
plt.ylabel("Genre", fontsize=14)
plt.title("Popular Genres Among Genders", fontsize=16)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

令我惊讶的是,男性和女性确实喜欢相似的类型。男女最受欢迎的类型是戏剧,其次是喜剧。当然,我们考虑到在这个数据集中男性比女性多,当我们考虑建立我们的推荐系统时,我们也必须考虑到这一点。

需要知道的是,当我们对评分者的年龄加以限制时,兴趣是否会发生变化。

按性别划分,最受孩子欢迎的电影类型有哪些?

**注:**根据英国标准,成年人可以定义为> = 18 岁的人,因此儿童应该是< 18 岁。

full_df[full_df["age"] < 18][genres + ["gender"]].groupby("gender").sum().T.plot(kind="barh", figsize=(12, 5), color=["orange", "blue"])
plt.xlabel("Counts",fontsize=14)
plt.ylabel("Genre", fontsize=14)
plt.title("Popular Genres Among Children by Gender", fontsize=16)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

戏剧仍然很受 18 岁以下男性的欢迎,但更多 18 岁以下的男性更喜欢喜剧和动作片。另一方面,18 岁以下的女性几乎没有改变,仍然是戏剧和喜剧。

这些数字很有趣,但我想知道戏剧和喜剧在男性和女性中的流行是因为这些类型的电影通常被认为是最好的电影类型(因此它们获得了最多的观看和评级),还是因为这些标签与最多的电影相关联。

什么类型的电影最受欢迎?

:一部电影可以有多种类型(例如,一部电影可以是动画、儿童和喜剧)

# get the genre names in the dataframe and their counts
label= item_df.loc[:, "unknown":].sum().index
label_counts= item_df.loc[:, "unknown":].sum().values# plot a bar chart
plt.figure(figsize=(12, 5))
plt.barh(y= label, width= label_counts)
plt.title("Genre Popularity", fontsize=16)
plt.ylabel("Genres", fontsize=14)
plt.xlabel("Counts", fontsize=14)plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如我所想,戏剧和喜剧标签与样本中的大多数电影相关联。也许电影制作人意识到我们需要笑声和一些戏剧,因此他们利用了这一点——这是我们可以研究的。

接下来,我们观察每个流派的平均收视率…

各流派的收视率分布如何?

:密度图用于观察数据集中变量的分布。

# https://github.com/HarilalOP/movielens-data-exploration/blob/master/src/main/code/exploratory_analysis.ipynb
df_temp = full_df[['movie_id','rating']].groupby('movie_id').mean()# Histogram of all ratings
df_temp.hist(bins=25, grid=False, edgecolor='b', density=True, label ='Overall', figsize=(15,8))# KDE plot per genre
for genre in genres:
    df_temp = full_df[full_df[genre]==True][['movie_id','rating']].groupby('movie_id').mean()
    df_temp.rating.plot(grid=True, alpha=0.9, kind='kde', label=genre)
plt.legend()
plt.xlim(0,5)
plt.xlabel('Rating')
plt.title('Rating Density plot')
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于大多数类型来说,情节主要是向左倾斜的——这可能是因为用户更愿意评价他们喜欢的电影,因为如果他们不喜欢电影,人们不会真的看电影。我们必须进行一些研究,看看我们的情况是否如此。

好吧,最后一个情节比较复杂。我们可以通过更具体地观察用户来再次简化事情。

按性别划分的年龄分布是怎样的?

# creating new variable for ages of all males and females
female_age_dist = user_df[user_df["gender"] == "F"]["age"]
male_age_dist = user_df[user_df["gender"] == "M"]["age"]

# plotting boxplots 
plt.figure(figsize=(12,5))
plt.boxplot([female_age_dist, male_age_dist])
plt.xticks([1, 2], ["Female", "Male"], fontsize=14)
plt.title("Age Distribution by Gender", fontsize=16)

plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

男性年龄分布有一些异常值,女性年龄中位数略高于男性。此外,女性年龄分布框比男性年龄分布框长,这意味着女性年龄分布比男性年龄分布更分散。

用户中最常见的职业是什么?

# creating the index and values variables for occupation
occ_label= user_df.occupation.value_counts().index
occ_label_counts = user_df.occupation.value_counts().values

# plot horizontal bar chart
plt.figure(figsize=(12,5))
plt.barh(y=occ_label, width=occ_label_counts)
plt.title("Most common User Occupations", fontsize=16)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不足为奇的是,数据集中的大多数人是学生。我们来看看各职业给出的平均评分。

给定职业的平均评分是多少?

# creating a empty df to store data
df_temp = pd.DataFrame(columns=["occupation", "avg_rating"])

# loop through all the occupations 
for idx, occ in enumerate(occ_label):
    df_temp.loc[idx, "occupation"] = occ 
    df_temp.loc[idx, "avg_rating"] = round(full_df[full_df["occupation"] == occ]["rating"].mean(), 2)

# sort from highest to lowest
df_temp = df_temp.sort_values("avg_rating", ascending=False).reset_index(drop=True)
df_temp

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

包裹

在这一点上停下来是困难的,因为我们可以从这些数据中提取更多的见解。我个人认为数据可视化并没有真正的尽头,所以应该由做可视化的人来决定何时停止。一个好的指标可能是当我们相信我们已经对数据有了足够的了解,可以开始建立一个有效的基线模型(如果我们还没有的话)。在构建模型时,我们可以随时返回并迭代我们的可视化,以基于我们的模型预测从我们的数据中获得更多的洞察力。

本文使用jupyter_to_medium制作。

[## 从 Jupyter 笔记本发布到媒体

在媒体上创建技术职位的更有效方法

towardsdatascience.com](/publishing-to-medium-from-jupyter-notebooks-53978dd21fac)

让我们继续 LinkedIn 上的对话…

[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn

在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有两个工作列在他们的…

www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)

监控二元类最大似然预测模型

原文:https://towardsdatascience.com/comprehensive-guide-for-ml-model-monitoring-4ab4f66faf70?source=collection_archive---------27-----------------------

回顾 ML 模型的稳定性和性能

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

克里斯·利维拉尼在 Unsplash 上的照片

简介:

随着技术和技能的进步,越来越多的公司开始对机器学习(ML)模型表现出信心。这反过来意味着越来越多的组织已经开始在他们的各种业务功能中使用 ML 模型。它在 ML 管道中开辟了另一条工作线,叫做“模型监控和模型评审”。ML 渠道中的这一系列工作对于具有高再培训周转时间的模型变得极其重要,银行领域的 ML 模型就是一个例子。在这种情况下,模型被训练一次,并被用于定期预测,例如,被训练一次的信用卡使用模型可以被营销团队用于每周/每月预测一组新的观察值。那么问题来了,我们如何知道 6 个月前训练好的模型仍然足够好?这种模式有多“稳健”?在本文中,我将尝试为二元类预测模型回答这些问题。

在本文中,我们将以贷款占用预测模型为例。该模型预测每个客户为 1 或 0。

1:客户将在未来 30 天内接受贷款,

0:客户在未来 30 天内不会接受贷款

车型回顾:

该模型评审框架不知道用于构建该模型的 ML 技术。

总的来说,模型评审框架可以分为两组指标,“稳定性指标”和“性能指标”。

  1. 稳定性指标:

答:人口稳定指数

b .特征稳定性指数(CSI)

2。绩效指标:

a .基尼系数和基尼系数——统计数据

b .增益矩阵:增益图,等级排序

数据集:

对于审查过程,我们需要两个数据集:

**A .开发数据集:**构建模型的原始数据集。该数据集中需要的字段有:

i .唯一观察标识符,如果模型是为客户建立的,它可以是客户 ID

二。实际因变量标志(实际 0 和 1)表示客户是否将在未来 30 天内贷款

三世。模型开发期间的预测概率值

**B .查看数据集:**要查看模型的数据集。例如,我们假设贷款占用模型是基于 2019 年 1 月的一些数据和一组变量构建的,您希望检查 2019 年 6 月的模型有多稳健,那么在这种情况下,审查数据集将具有模型对 6 月做出的预测。该数据集需要的字段有:

一、唯一标识符

二世。实际因变量标志(实际 0 和 1)表示客户是否将在未来 30 天内贷款

三。预测概率值

指标:

为了检查模型性能有多好,我们将跨开发和审查数据集计算以下指标。

稳定性指标:

稳定性度量测量模型相对于时间的稳定性。这些指标主要评估预测概率的分布在多个预测中是否具有可比性。

1。PSI:

PSI 代表人口稳定指数。它基本上将回顾数据集中的预测概率分布与开发数据集中的预测概率分布进行比较。这里的想法基本上是检查,“评审概率如何与开发数据集的概率相比较”。

PSI = [(基于审查数据集®中预测概率的记录百分比)–

(基于开发数据集(D)中预测概率的记录百分比)】 ln(R/D)*

计算 PSI 的步骤:

  1. 按照预测概率降序排列开发数据集
  2. 将数据集分成 10 或 20 组(十分位数)
  3. 计算每组观察值的百分比(D)
  4. 对于步骤(2)中形成的每个组,找到概率值的最小值和最大值,基本上计算开发数据集中每个组的边界值
  5. 用这些概率边界值在审查数据集中形成 10 组,计算每组审查数据集中的观察值百分比®
  6. 计算步骤(5)和步骤(3)之间的差值,即(R-D)
  7. 取步骤(v)/步骤(iii)的自然对数,即 ln(R/D)
  8. 将步骤(6)和(7)相乘
  9. 对步骤(8)中得到的所有行的值求和,求和值为 PSI

从 PSI 推断:

  • 如果PSI<0.1则没有变化,模型是健壮的,可以继续使用现有的模型
  • 如果PSI>= 0.1和< 0.2,则需要对型号稍加改动
  • 如果PSI>= 0.2那么你应该重新训练该型号

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

十分位数人口稳定指数表。

2。老何:

CSI 代表特征稳定性指数。如果 PSI 超出可接受的值范围,CSI 可以帮助我们了解哪些变量导致了审查数据集中概率分布的变化。CSI 的计算方法与 PSI 完全相同。我们不再考虑模型预测的概率,而是一次取一个独立变量,以完全相同的方式计算 CSI。

性能指标:

1。基尼系数和基尼系数——统计:

**基尼:**基尼系数或基尼指数衡量一个变量的值之间的不平等。指数值越高,数据越分散。因此,根据预测概率计算的基尼系数给出了基尼系数的离散程度。可以计算发展和审查数据集的基尼系数。两个基尼值之间的差异应该尽可能小。基尼系数的详细解释和计算方法在这里有解释。预测概率的基尼系数可以通过为两个十进制舍入概率值(即 0.00、0.01、0.02、0.03)创建 101 个相等的观察组来计算,然后遵循这里提到的步骤。

KS 统计: KS 统计代表 Kolmogorov Smirnov 统计。这是一种衡量积极因素(事件或 1)与消极因素(非事件或 0)的区分程度。如果 KS 值为 100%(或 1),这意味着模型能够将群体分成两组,其中一组包含所有阳性,另一组包含所有阴性。在这个模型评审框架中,为开发和评审数据集计算 KS 统计。KS 值的差异应该尽可能小。详细的分步计算可以在这里找到。

从基尼和 KS 推断:

开发和评审数据集之间的 Gini 和 KS 之差应小于 10%。如果该值大于 10%,我们需要重新训练模型。

2。增益矩阵:

增益和提升图是众所周知且易于使用的模型评估框架。它们衡量的是,与没有模型的情况相比,使用预测模型的情况会好到什么程度。创建收益矩阵的步骤计算如下(遵循开发和审查数据的步骤,一次一个)

  1. 按照贷款吸收预测概率值的降序对数据集进行排序
  2. 将数据集分成 10 等份
  3. 计算每十分位数的观察次数
  4. 计算每十分位数中实际阳性的数量(事件或 1)
  5. 计算每十分位数中实际阳性的累积
  6. 计算每十分位数中实际事件的百分比,获得分数
  7. 计算事件发生率,即每十分位数/观察总数中的累积事件

你可以在这里找到详细的步骤和 Python 代码来计算同样的

执行上述开发和审查数据集的步骤。

a .增益分数 : 开发和评审数据集的绘图增益分数与累积人口百分比。这两种趋势应该是相似的,相互接近的。

b .等级排序 : 地块事件发生率与累计人口百分比,再次为开发和评审数据集,且趋势应相互接近。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从增益矩阵推断:

增益得分和等级排序图在开发和审查数据集中应具有相同的趋势。我们得出的每组 10 个十分位数之间的差值应该在 5%的范围内。如果这些值相差超过 5%,则需要再次训练该模型。

结论:

PSI、基尼系数、KS 统计和增益矩阵给出了如何在不同的预测概率得分值之间分布观察值的概念。如果评审数据集中的这些度量反映了它们在开发数据集中的值,那么模型就像预期的那样执行。或者,如果评审数据集中的这些度量与开发数据集中的不同,您需要重新训练您的模型。

参考资料:

  1. https://www . LinkedIn . com/pulse/credit-risk-score card-monitoring-tracking-shailendra
  2. https://towards data science . com/Gini-coefficient-and-Lorenz-curve-f 19 bb 8 f 46d 66
  3. https://www . analyticsvidhya . com/blog/2019/08/11-重要-模型-评估-错误-度量/
  4. https://towards data science . com/how-to-determine-the-best-model-6b 9 c 584d 0 db 4
  5. https://unsplash.com/photos/dBI_My696Rk

基于项目的协同过滤综合指南

原文:https://towardsdatascience.com/comprehensive-guide-on-item-based-recommendation-systems-d67e40e2b75d?source=collection_archive---------1-----------------------

关于基于项目的推荐系统如何工作以及如何在实际工作环境中实现它的详细指南。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

推荐系统已经存在很长时间了。Youtube、脸书、亚马逊和许多其他网站向他们的用户提供某种推荐。这不仅有助于他们向用户展示相关产品,还能让他们在竞争对手面前脱颖而出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

个性化体验对业务 KPI 的影响(来源 bluevenn

一种向用户推荐项目的技术是基于项目的推荐系统,也称为项目-项目协同过滤或 IBCF。在本指南中,我们将介绍该算法的所有细节,其背后的实际数学将在 R 中实现,首先不使用任何库,这将有助于理解该算法如何适应数据集,然后使用 recommenderlab(一个 R 包)实现该算法,以展示在真实环境中如何工作。

如果你也对评估推荐系统的性能感兴趣,看看 这篇文章解释了如何评估推荐系统的性能

项目—项目协同过滤

项目-项目协同过滤是一种基于用户已经喜欢或积极互动的项目寻找相似项目的推荐方法。它是亚马逊在 1998 年开发的,对亚马逊的成功起了很大的作用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Sidharth 在 spatnaik77 上拍摄的照片

IBCF 的工作原理是,它根据用户以前消费过的商品来推荐商品。它查找用户已经消费过的商品,然后找到与消费过的商品相似的其他商品并相应地推荐。

我们用一个例子来理解这个。假设我们的用户 Jhone 想要购买一部电影 DVD。我们的工作是根据他过去的喜好给他推荐一部电影。我们会先搜索 Jhone 看过或者喜欢过的电影,我们姑且把那些电影称为‘A’、‘B’、‘C’。接下来,我们将搜索与三部电影相似的其他电影。假设我们发现电影“D”与“C”高度相似,因此,约翰很有可能也会喜欢电影“D ”,因为它与约翰已经喜欢过的电影相似。因此,我们将向约翰推荐电影《D》。

因此,IBCF 的核心就是寻找与用户已经喜欢的商品相似的商品。但是如何找到相似的物品呢?如果有多个相似的项目,那么应该先推荐哪个项目呢?为了理解这一点,让我们首先理解过程背后的直觉,这将帮助我们理解 IBCF 推荐过滤背后的数学原理。

如果你想更深入地了解基于项目的过滤,我建议从 这个课程开始 。导师不仅解释了算法的所有细节,还用真实世界的例子解释了它的应用,这对任何想在推荐系统领域进步的人都是有价值的。

查找项目之间的相似性

假设我们有一个用户和他们对电影的评分表(链接):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

评级数据集(由 Muffaddal 提供)

让我们挑选两部电影(Id 1:玩具总动员和 Id 2:星球大战),我们必须计算它们的相似度,即这两部电影在用户相似度方面有多少可比性。为了计算这一点,我们将:

首先将两部电影的多个评分互相比较,然后将结果相加。让我们称这个值为’ A’

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

两个用户评级的乘积(由 Muffaddal)

其次,我们将对电影评分的平方求和,然后求它们的平方根。因此,平方所有电影 1 的评级,将它们相加,然后取平方根得到最终值(对电影 2 做同样的操作)。这样做将得到两个值,即电影 1 和电影 2 的平方根值。将两个值相乘。我们称这个最终值为’ B’

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

等级平方和的平方根(由 Muffaddal)

第三,将 A 和 B 分开,这将得到一个分数,表明电影 1 和电影 2 彼此有多接近(链接)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

电影 1 和 2 的相似之处(穆法达尔)

对所有电影重复上述过程将产生一个表格,其中包含每部电影之间的相似之处(通常我们称之为项目)。

下面是如何用数学形式描述上述过程的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

相似性方程(Muffaddal)

不要被这个公式看起来有多疯狂所压倒。这真的很简单,也正是我们在上面的 excel 练习中所做的。让我们一点一点的分解,来理解这些怪异的符号是什么意思。

在字母上加一些标签将有助于理解等式的每一部分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

余弦相似方程(Muffaddal)

从标签 1(左边的蓝色)开始,上面说明了为了计算项目“I”和项目“j”之间的相似性(将“I”和“j”视为电影 id 1 和 2),将用户“u”给出的项目“I”和“j”的所有评级相乘并求和。将结果除以用户“u”给出的各个项目的评分平方和的平方根乘积。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

余弦相似方程(Muffaddal)

这正是我们在上面的 excel 练习中所做的。对以上所有项目进行迭代将产生一个值列表,该列表将指示其他项目与我们的主项目“I”的接近程度。这种方法也被称为余弦相似度。它有助于计算两个向量之间的距离。

余弦相似性是可行的,但是它没有考虑用户的乐观行为。不同的用户可以根据他们的乐观程度对相同的项目做出不同的评价。在 5 的尺度上,一个人可以给一个项目评分 5,而另一个人可以给 3,即使他们都非常喜欢这个项目。为了说明这一点,我们必须对我们的相似性公式做一个小小的改变。它看起来是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调整后的余弦相似性方程(Muffaddal)

用用户的平均评分减去给定项目的用户评分,将评分标准化到相同的等级,并有助于克服乐观主义问题。我们称之为调整余弦相似度。

还有一种类似的方法,不是减去用户的平均评分,而是减去项目的平均评分。这有助于了解给定用户评分与平均项目评分的偏差。这种技术被称为皮尔逊相似度。余弦和皮尔逊都是广泛使用的计算相似性的方法。

将调整后的余弦相似性方程应用于项目评级将生成一个表格或矩阵,显示一个项目与另一个项目的相似程度。它应该是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调整后的相似性矩阵(由 Muffaddal)

计算推荐得分

一张相似物品的桌子已经完成了一半的工作。我们知道哪些项目是可比较的,但我们还没有解决从相似项目列表中向用户推荐哪些项目的问题。为此,必须将我们的相似度矩阵与用户过去的评分项目历史结合起来,以生成一个推荐。这很容易通过应用 IBCF 方程来实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

基于项目的推荐系统方程(Muffaddal)

u =我们正在为其生成推荐的用户
i =考虑中的项目,即该项目是否应该被推荐
score(u,i) =生成一个分数,该分数将指示项目‘I’对我们的用户‘u’的推荐有多强。
j =与主要项目 I 相似的项目

上面的等式得出,为了计算用户“u”的项目“I”的推荐分数,将项目“I”和“j”的相似性与用户“u”对项目“j”给出的评级和项目“j”的平均评级之差的乘积求和。将结果除以项目“I”和“j”的相似度之和,将输出与用户“u”的平均评分相加。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

基于项目的推荐系统方程(Muffaddal)

这样做将为用户和可用项目生成一个得分矩阵。可以向用户推荐得分最高的项目。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用户方程的推荐分数矩阵(Muffaddal)

使用 R 实现 IBCF

让我们看看上面使用 R ( 代码链接)的操作。这将使我们进一步了解 IBCF。

注意:我们将使用循环和数据帧来实现 IBCF,因为我们知道 R 在循环中相当慢,而矩阵的行/列乘法比数据帧快。原因是这个练习的主要目的是帮助理解 IBCF 是如何使用编程实现的,我们对编写健壮的代码不感兴趣。将在本文的第三部分展示一种更快的替代方法。

说到这里,让我们开始加载所需的库和电影分级数据集

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

R 中的评级数据集(由 Muffaddal)

为了根据电影评分计算 IBCF,首先,我们将通过用用户的平均评分减去项目评分来计算标准化评分。这将使评级标准化到相同的等级。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

R 中的归一化评级矩阵(由 Muffaddal)

接下来,我们将通过将实际评分和标准化评分传递给我们的“calCosine”函数来计算每个项目的相似度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

R 中调整的相似矩阵(由 Muffaddal)

现在是时候通过迭代每个用户和项目来计算每个用户的推荐分数了。在每次迭代中,首先,我们将计算用户的平均评分,这将用于计算分数,其次,我们检查项目是否由用户评级,如果评级,那么我们存储-1,因为我们只对推荐用户没有评级或不喜欢的项目感兴趣。

如果该项目没有评级,然后我们抓住前 10 个项目类似于我们的给定项目使用相似性矩阵,我们计算以上。然后,我们获取用户对这些相似项目的评分。我们还将计算类似项目的平均评级,以便将其与项目的评级相减。

最后,我们将传递一个类似的项目列表,类似项目的用户评级历史列表,以及类似项目的平均评级到我们的’ calScore '分数函数。结果将添加到用户的平均评分中。这样做将产生分数,该分数将指示向哪个用户推荐哪个项目。分数越高,用户购买该商品的可能性越大。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

R 中的推荐分数(由 Muffaddal 提供)

预测推荐分数有助于理解和建议项目,但我们可以通过用项目名称替换分数来进一步改善结果的显示方式。下面是怎么做的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

R 中的推荐项目(Muffaddal)

实现以上内容将增强我们对推荐系统如何工作的理解。但是上面的代码效率不高,而且相当慢。有许多 R 包,使用它们我们可以毫无困难地实现推荐过滤。一个这样的包是推荐者实验室

利用推荐实验室实现 IBCF

现在,让我们使用推荐者实验室来实现 IBCF。

recommenderLab 在“真实评级矩阵”数据集上工作,因此我们必须首先将我们的数据框架转换成它。

接下来,我们将数据集分为训练集和测试集。我们将使用训练集来为 IBCF 建立模型,并在测试集上评估其结果。

是时候使用 recommenderLab 实现项目-项目推荐系统了

就是这样。这一行代码计算了我们用自己的 R 代码实现的所有内容。

当我们提供训练数据时,这是我们的模型所预测的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 recommenderLab 推荐的项目(Muffaddal)

结论

项目-项目协同过滤是一种推荐系统,它基于使用用户给项目的评级计算的项目之间的相似性。它有助于解决基于用户的协作过滤器所遇到的问题,例如当系统有许多项目而评价的项目较少时。IBCF 的计算强度也不如 UBCF。实现 IBCF 有助于构建一个强大的推荐系统,可以用来向用户推荐商品。

相似读取

[## 评估推荐系统的详尽方法列表

本文解释了一些评估推荐系统性能的技术。

towardsdatascience.com](/an-exhaustive-list-of-methods-to-evaluate-recommender-systems-a70c05e121de) [## 使用 BigQuery ML 进行 RFM 分析

使用 BigQuery ML 中的 RFM 分析和 Data Studio 中的可视化进行用户细分。

towardsdatascience.com](/rfm-analysis-using-bigquery-ml-bfaa51b83086)

参考

[1]评级数据:

[## IBCF -电影评分. xlsx

收视率用户,1:玩具总动员(1995),2:星球大战:第六集-绝地归来(1983),356:阿甘正传(1994),318…

docs.google.com](https://docs.google.com/spreadsheets/d/1ylQyvq7slR0Vjj9hX4VkpFAnidaPRhnv/edit#gid=602195593)

[2]代码链接:

[## muffaddal 52/基于项目的协同推荐系统

R. Contribute 中基于项目的协作系统代码 muffaddal 52/基于项目的协作推荐系统…

github.com](https://github.com/muffaddal52/Item-Based-Collaborative-Recommendation-System/blob/master/Item-Item%20Collaborative%20Filters.R)

近似最近邻算法综合指南

原文:https://towardsdatascience.com/comprehensive-guide-to-approximate-nearest-neighbors-algorithms-8b94f057d6b6?source=collection_archive---------2-----------------------

实践中的搜索-近似最近邻

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源:https://en . Wikipedia . org/wiki/Proximity _ analysis #/media/File:Euclidean _ Voronoi _ diagram . SVG

最近邻动机

如今,随着用户在短时间内从互联网上获取越来越多的信息,对高效的搜索方式的需求也越来越大。这也是为什么“最近邻居”成为一个热门的研究课题,以增加用户在合理的时间内找到所要找信息的机会。

“最近邻”的用例是无穷无尽的,它在许多计算机科学领域都有使用,比如图像识别、机器学习和计算语言学( 12 等等)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在无止境的使用案例中,有网飞的推荐、Spotify 的推荐、Pinterest 的视觉搜索,以及更多令人惊叹的产品。亚历山大·沙托夫在 Unsplash 上拍摄的照片

为了计算精确的最近邻,存在以下技术:

  • 穷举搜索- 将每个点与每隔个点的进行比较,这将需要线性查询时间(数据集的大小)。
  • 网格技巧- 将空间细分成网格,这将需要指数空间/时间(在数据集的维度上)。
    因为我们在谈论高维数据集,这是不切实际的。

穷举搜索用法

我将向展示如何找到相似的向量,并将使用movie lens数据集来完成(包含 100k 行),方法是使用数据集的丰富版本(已经包含电影标签及其语义表示)。本文的全部代码可以在 Jupyter 笔记本这里找到。

首先,我们将加载我们的数据集,它已经由电影标签和它们的语义表示组成,在这里计算

import pickle
import faissdef load_data():
    with open('movies.pickle', 'rb') as f:
        data = pickle.load(f)
    return datadata = load_data()
data

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如我们所看到的,数据实际上是一个字典,name 列由电影的名称组成,vector 列由电影的向量表示组成。

我将展示如何使用faiss**进行彻底的搜索。**我们首先要创建索引类。

class ExactIndex():
    def __init__(self, vectors, labels):
        self.dimension = vectors.shape[1]
        self.vectors = vectors.astype('float32')
        self.labels = labels    

     def build(self):
        self.index = faiss.IndexFlatL2(self.dimension,)
        self.index.add(self.vectors)

    def query(self, vectors, k=10):
        distances, indices = self.index.search(vectors, k) 
        # I expect only query on one vector thus the slice
        return [self.labels[i] for i in indices[0]]

在定义了 index 类之后,我可以使用下面的代码片段用我的数据集构建索引。

index = ExactIndex(data["vector"], data["name"])
index.build()

现在很容易搜索,假设我想搜索与“玩具总动员”最相似的电影(它位于索引号 0 中),我可以编写以下代码:

index.query(data['vector'][0])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就这样,我们已经做了精确的搜索,我们现在可以去午睡了:)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Lidya NadaUnsplash 上拍摄的照片

但不全是彩虹和独角兽:

不幸的是,大多数现代应用程序都有大量高维(数百或数千)数据集,因此线性扫描需要一段时间。如果这还不够,通常还会有额外的限制,比如合理的内存消耗和/或低延迟。

值得注意的是,尽管最近在这个主题上取得了很多进展,但是保证检索精确最近邻居的唯一可用方法是穷举搜索(由于维度的 T2 诅咒)。)

这使得精确最近邻变得不切实际,甚至允许 【近似最近邻】 (安)进入游戏。如果我们愿意牺牲一些准确性,相似性搜索可以快几个数量级。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Niklas KicklUnsplash 上拍摄

近似最近邻游戏攻略

为了给出为什么近似最近邻可能足够好的小直觉,我将给出两个例子:

  • **视觉搜索:**作为一个用户,如果我寻找一张蜜蜂图片,我不介意从这三张图片中得到哪一张。
  • **推荐:**作为一个用户,我并不真的介意最近邻的顺序或者即使我只有十个最佳候选中的八个。

近似最近邻技术通过将数据预处理成有效的索引来加速搜索,并且通常使用以下阶段来解决:

  • 向量变换 —在向量被索引之前应用于向量,其中包括维度缩减和向量旋转。为了这篇文章结构良好又有几分简洁,我就不讨论这个了。
  • 矢量编码 —应用于矢量,以构建实际的搜索索引,其中包括基于数据结构的技术,如树、LSH 和量化,这是一种将矢量编码为更紧凑形式的技术。
  • 无穷举搜索组件 —应用于矢量以避免穷举搜索,在这些技术中有倒排文件和邻域图。

使用树的矢量编码

介绍和直觉

当谈到人工神经网络时,基于树的算法是最常见的策略之一。他们通过将数据集分成子集来构建森林(树的集合)作为他们的数据结构。

最突出的解决方案之一是,它使用树木(更准确地说是森林)来实现 Spotify 的音乐推荐。既然有综合解释我这里只提供背后的直觉,应该怎么用,利弊。

在这种情况下,为了构造索引,我们创建了一个森林(也称为许多树),每棵树都是以如下方式构造的,我们随机选取两个点,并通过它们的超平面将空间一分为二,我们不断递归地分裂成子空间,直到与一个节点相关的点足够小。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传****外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源:https://Erik Bern . com/2015/10/01/nearest-neighbors-and-vector-models-part-2-how-to-search-in-high-dimensional-spaces . html

为了搜索构建的索引,遍历森林以获得一组候选点,从这些候选点返回最接近查询点的点。

讨厌的用法

我们将像以前一样创建一个索引类。我们要用惹恼库。可以想象,大多数逻辑都在构建方法(索引创建)中,其中准确性-性能的权衡由以下因素控制:

  • number_of_trees —我们构建的二叉树的数量,一个较大的值会给出更准确的结果,但是索引也更大。
  • search_k —我们对每个点搜索的二叉树的个数,值越大会给出更准确的结果,但返回的时间会更长。
**class** Annoy**Index**():
    **def** __init__(self, vectors, labels):
        self.dimension = vectors.shape[1]
        self.vectors = vectors.astype('float32')
        self.labels = labels    

    **def** build(self, number_of_trees=5):
        self.index = annoy.AnnoyIndex(self.dimension)
        **for** i, vec **in** enumerate(self.vectors):
            self.index.add_item(i, vec.tolist())
        self.index.build(number_of_trees)

    **def** query(self, vector, k=10):
        indices = self.index.get_nns_by_vector(
              vector.tolist(), 
              k, 
              search_k=search_in_x_trees)                                           
        **return** [self.labels[i] **for** i **in** indices]

在我定义了烦人的索引类之后,我可以使用下面的代码片段用我的数据集构建索引。

index = AnnoyIndex(data["vector"], data["name"])
index.build()

现在搜索相当容易,假设我想搜索与“玩具总动员”最相似的电影(它位于索引号 0)。

index.query(data['vector'][0])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就是这样,我们已经有效地搜索了类似“玩具总动员”的电影,并得到了近似的结果。

值得注意的是,我将针对每种实现而不是每种技术来说明利弊。

惹恼专业人士

  • 将索引创建与加载分离开来,这样就可以将索引作为文件传递,并快速将它们映射到内存中。
  • 我们可以调整参数来改变精度/速度的折衷。
  • 它有能力使用静态文件作为索引,这意味着你可以跨进程共享索引。

惹怒弊

  • 精确的最近邻可能跨越边界到达相邻单元之一。
  • 不支持 GPU 处理。
  • 不支持批处理,所以为了增加吞吐量“需要进一步的黑客攻击”。
  • 不能逐渐增加点数(烦恼 2 试图解决这个问题)。

使用 LSH 的矢量编码

介绍和直觉

就人工神经网络而言,基于 LSH 的算法是最常见的策略之一。它们通过将附近的点映射到同一个桶中来构建哈希表作为它们的数据结构。

最突出的实现之一是 facebook 的【Faiss】。由于有大量的 LSH 解释,我在这里只提供其背后的直觉,应该如何使用,利弊。

在 LSH 中,为了构造索引,我们应用多个哈希函数将数据点映射到桶中,使得彼此靠近的数据点以高概率位于相同的桶中,而彼此远离的数据点很可能落入不同的桶中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源:https://brc7.github.io/2019/09/19/Visual-LSH.html

为了搜索所构建的索引,查询点被散列以获得最接近的桶(一组候选点),从该桶返回最接近查询点的桶。

重要的是要注意,有一些改进我还没有检查,如飞行算法GPU 上的 LSH

LSH 用法

我将展示如何使用 faiss ,来“使用 LSH 近似最近邻”。
我们将创建 index 类,正如您所见,大多数逻辑都在 build 方法中(索引创建),您可以在其中控制:

  • num_bits —较大的值将给出更准确的结果,但索引也更大。
class LSHIndex():
    def __init__(self, vectors, labels):
        self.dimension = vectors.shape[1]
        self.vectors = vectors.astype('float32')
        self.labels = labels    

     def build(self, num_bits=8):
        self.index = faiss.IndexLSH(self.dimension, num_bits)
        self.index.add(self.vectors)

    def query(self, vectors, k=10):
        distances, indices = self.index.search(vectors, k) 
        # I expect only query on one vector thus the slice
        return [self.labels[i] for i in indices[0]]

在定义了 LSH 索引类之后,我可以使用下面的代码片段用我的数据集构建索引。

index = LSHIndex(data["vector"], data["name"])
index.build()

现在搜索相当容易,假设我想搜索与“玩具总动员”最相似的电影(它位于索引号 0)。

index.query(data['vector'][0])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就是这样,我们已经有效地搜索了类似“玩具总动员”的电影,并得到了近似的结果。

像以前一样,我将声明每个实现的优点和缺点,而不是每个技术。

LSH 的优点

  • 生成这些随机散列函数不需要诸如数据分布之类的数据特征。
  • 可以调整近似搜索的准确性,而无需重建数据结构。
  • 良好的亚线性查询时间的理论保证。

LSH 弊

  • 实际上,该算法可能比线性扫描运行得慢。
  • 不支持 GPU 处理。
  • 需要大量内存。

使用量化的矢量编码

量化动机

尽管我们通过构建索引来提高查询性能,但是我们没有考虑额外的约束。

像许多工程师一样,我们认为线性存储“不成问题”(由于像 S3 这样的系统)。然而在实践中,线性存储可能会变得非常昂贵非常快,一些算法需要将它们加载到 RAM 中,并且许多系统没有分离存储/计算,这一事实肯定不会改善这种情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这张图片显示了支持 X 张图片所需的价格来源:https://www.youtube.com/watch?v=AQau4-VF64w

这就是为什么当谈到人工神经网络时,基于量化的算法是最常见的策略之一。

量化是一种通过定义一个函数(量化器)将我们的数据编码成一个紧凑的近似表示来减少数据集大小(从线性的)的技术。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

给定一个数据集,构造表示我们的数据集的数字向量,然后将向量压缩为近似表示来源:https://medium . com/code-heroku/building-a-movie-recommendation-engine-in-python-using-sci kit-learn-c 7489d 7 CB 145

量子直觉

这种方法的直觉如下,我们可以通过在编码阶段用矢量的更精简的近似表示法替换每个矢量来减小数据集的大小。

实现更精简的近似表示的一种方式是给相同的表示提供相似的向量。这可以通过聚类相似的向量并以相同的方式表示每个向量来实现(质心表示),最流行的方法是使用 k-means

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个演示 k-means 内部工作原理的动画

由于 k-means 将空间中的向量分成 k 个簇,所以每个向量可以表示为这 k 个质心中的一个(最相似的一个)。

这将允许我们以更有效的方式来表示每个向量,每个向量 log(k)比特,因为每个向量可以用质心的标签来表示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的例子中,每个向量由一个质心表示。因为我们有 2042 个质心,所以我们可以用 11 位来表示每个向量,而不是 4096 ( 1024*32)。

但是,这种惊人的压缩带来了巨大的成本,我们失去了准确性,因为我们现在不能从质心分离原始向量

产品量化直觉

我们看到,使用像 k-means 这样的量化器是有代价的,为了提高矢量的精度我们需要大幅增加质心的数量,这使得量化阶段在实践中不可行**。**

这就是乘积量化的起源,我们可以通过将每个矢量分成许多矢量来大幅增加质心的数量,并对所有这些矢量运行量化器,从而提高精度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的例子中,每个向量由 8 个子向量表示,这 8 个子向量可以由质心之一表示。由于我们有 256 个质心,我们可以用 1 个字节表示每个矩阵,与 4096 ( 1024*32)相比,向量表示只有 8 个字节。

虽然与常规量化器相比,它增加了一点向量的大小,但它仍然是 O(log(k)),允许我们大幅提高精度,并且在实践中仍然有效。

不幸的是,在搜索方面,尽管我们可以使用查表和一些加法更有效地计算距离。我们仍将进行彻底的搜索。

使用以下算法完成搜索:

  • 用计算出的每个子向量和该子向量的每个质心之间的距离构建一个表。
  • 计算数据集中每个向量的近似距离值时,我们只需使用这些质心 id 在表中查找部分距离,并将它们相加!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的例子中,这意味着构建一个具有 256 行(每个质心一行)和 8 列(每个子向量一列)的子向量距离表。记住,每个数据库向量现在只是 8 个质心 id 的序列。

  • 精确的最近邻可能跨越边界到达相邻单元之一。

倒排文件索引直觉

该算法的直觉是,如果我们以这样一种方式分割我们的数据集**,我们可以避免穷举搜索,即在搜索时,我们只查询相关的分区(也称为 Voronoi 单元)。这在实践中效果很好的原因是因为许多数据集实际上是多模态的。**

然而,以这种方式划分数据集再次降低了准确性,因为如果查询向量落在最近的聚类的外围,那么它的最近邻居很可能位于多个附近的聚类中。

这个问题的解决方案很简单,就是搜索多个分区**(这也称为探测),搜索多个附近的分区显然需要更多的时间,但它给了我们更好的准确性。**

因此,正如我们现在看到的这完全是关于折衷,可以调整分区的数量和要搜索的分区的数量,以找到时间/精度折衷的最佳点。

值得注意的是,除了量化之外,倒排文件索引是一种可以与其他编码策略一起使用的技术。

编码残差直觉

算法的直觉是,我们希望相同数量的质心给出更精确的表示**。如果矢量没有以前那么明显,这是可以实现的。**

为了做到这一点,对于每个数据库向量,不是使用 PQ 来编码原始数据库向量,而是编码向量相对于其分区质心的偏移。

**然而,用它们的偏移量替换向量将 **再次增加搜索时间,因为我们将需要为我们探测的每个分区计算单独的距离表,因为每个分区的查询向量是不同的。

显然,这种权衡是值得的,因为 IVFPQ 在实践中运行得相当好。

具有倒排文件索引的产品量化

算法如下,我们用 k-means 聚类提前对数据集进行分区,以产生大量的数据集分区(倒排索引)。然后,对于每个分区,我们运行常规乘积量化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后,对于每个分区,我们将把它的每个向量分解成 D/M 个子向量,这将把我们的 N×D 矩阵转换成 N×M 的 D/M 矩阵。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的示例中,我们将把每个 1024 个向量分成 8 个 128 个向量,因此我们将数据集视为大小为 10k x 128 的 8 个矩阵。

然后我们将在每个子矩阵上运行 k-means 算法,这样每个子向量(行)将连接到 k 个质心之一。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的例子中,我们将在 k=256 的 8 个矩阵上运行 k 均值。这意味着,我们的每一行都连接到每个矩阵上的这 256 个行中的一个(原始矩阵中的每一行都连接到 8 个质心)。

我们将用最接近的匹配质心的 id 替换每个子向量。这就是我们所期待的,因为在前面的部分之后我们已经重复了元素,我们现在可以用一个非常小的标签来表示它们中的每一个,并且只保留一次实际值。

如果你想得到更多的理论,你可以看这个令人惊奇的视频。

使用倒排索引的乘积量化

我们将创建 index 类,正如您可以看到的,大多数逻辑都在 build 方法(索引创建)中,您可以在其中控制:

  • 子向量大小 —子向量的目标大小(产品量化阶段)。
  • number_of_partitions —用来划分数据集的分区数量(反向文件索引阶段)。
  • search_in_x_partitions —要搜索的分区数量(反向文件索引阶段)。
class IVPQIndex():
    def __init__(self, vectors, labels):
        self.dimension = vectors.shape[1]
        self.vectors = vectors.astype('float32')
        self.labels = labels    def build(self, 
              number_of_partition=8, 
              search_in_x_partitions=2, 
              subvector_size=8):
        quantizer = faiss.IndexFlatL2(self.dimension)
        self.index = faiss.IndexIVFPQ(quantizer, 
                                      self.dimension, 
                                      number_of_partition, 
                                      search_in_x_partitions, 
                                      subvector_size)
        self.index.train(self.vectors)
        self.index.add(self.vectors)

    def query(self, vectors, k=10):
        distances, indices = self.index.search(vectors, k) 
        # I expect only query on one vector thus the slice
        return [self.labels[i] for i in indices[0]]

在定义了 IVPQIndex 类之后,我可以使用下面的代码片段用我的数据集构建索引。

index = IVPQIndex(data["vector"], data["name"])
index.build()

现在很容易搜索,假设我想搜索与“玩具总动员”最相似的电影(它位于 0 索引中)。

index.query(data['vector'][0:1])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就这样,我们使用 IVPQ 有效地搜索了类似“玩具总动员”的电影,并得到了近似的结果。

带倒排文件优点的产品量化

  • 具有子线性空间、大压缩比(每个向量 log(k)比特)的唯一方法。
  • 我们可以调整参数来改变精度/速度的折衷。
  • 我们可以调整参数来改变空间/精度的权衡。
  • 支持批量查询。

带反向文件 Cons 的产品量化

  • 精确的最近邻可能跨越边界到达相邻单元之一。
  • 不能逐渐增加点数。
  • 精确的最近邻可能跨越边界到达相邻单元之一。

分层可导航小世界图

这种方法的直觉如下,为了减少图上的搜索时间,我们希望我们的图有一个平均路径。

这与著名的“六个握手规则”的说法紧密相连。

“你和地球上的任何人之间最多有 6 度的距离.”— 弗里吉斯·卡琳蒂

平均而言,许多真实世界的图是高度聚集的,并且倾向于具有彼此靠近的节点,这在形式上被称为小世界图:

  • 高度可传递性(社区结构)通常是分等级的。
  • 小平均距离~log(N)。

为了进行搜索,我们从某个入口点开始,迭代遍历图。在遍历的每一步,该算法检查从查询到当前基节点的邻居的距离,然后选择距离最小的相邻节点作为下一个基节点,同时不断跟踪最佳发现的邻居。当满足某个停止条件时,搜索终止。

分层可导航小世界图用法

我将展示如何使用 nmslib ,来“使用 HNSW 近似最近邻”。

我们将创建 index 类,正如您所看到的,大多数逻辑都在 build 方法中(索引创建)。

class NMSLIBIndex():
    def __init__(self, vectors, labels):
        self.dimention = vectors.shape[1]
        self.vectors = vectors.astype('float32')
        self.labels = labelsdef build(self):
        self.index = nmslib.init(method='hnsw', space='cosinesimil')
        self.index.addDataPointBatch(self.vectors)
        self.index.createIndex({'post': 2})

    def query(self, vector, k=10):
        indices = self.index.knnQuery(vector, k=k)
        return [self.labels[i] for i in indices[0]]

在定义了 NMSLIB 索引类之后,我可以使用下面的代码片段用我的数据集构建索引。

index = NMSLIBIndex(data["vector"], data["name"])
index.build()

现在搜索相当容易,假设我想搜索与“玩具总动员”最相似的电影(它位于索引号 0)。

index.query(data['vector'][0])

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就是这样,我们已经有效地搜索了类似“玩具总动员”的电影,并得到了近似的结果。

像以前一样,我将声明每个实现的优点和缺点,而不是每个技术。

分层可导航小世界图优点

  • 我们可以调整参数来改变精度/速度的折衷。
  • 支持批量查询。
  • NSW 算法具有多对数时间复杂度,并且在许多真实数据集上可以优于竞争对手的算法。

分层可导航小世界图 Cons

  • 精确的最近邻可能跨越边界到达相邻单元之一。
  • 不能逐渐增加点数。
  • 需要相当多的内存。

选择正确的近似最近邻算法

什么会影响我们的决定

评估应该使用哪些算法以及何时使用取决于用例,并且会受到以下指标的影响:

  • 速度- 指标创建和指标构建。
  • 硬件和资源- 磁盘大小,RAM 大小,以及我们是否有 GPU。
  • 精度要求。
  • 访问模式— 查询的数量,是否批处理,以及我们是否应该更新索引。

重要的是要注意,没有完美的算法,所有的都是关于权衡,这就是为什么这个主题如此有趣。

我们如何挑选

我创建了一个有点天真的流程图,以便允许人们选择应该为他的用例以及我们上面定义的度量选择哪种技术和实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每种实现都有自己的参数,这些参数会影响精度/速度权衡或空间/精度权衡。

遗言

本文一开始,我们展示了最近邻算法提供的价值,然后我列出了在现代应用中使用这些算法的问题,这些问题导致了“近似最近邻技术的诞生”。

然后,我解释了领先技术背后的直觉以及如何在实践中使用它们,这些技术允许我们在存储/准确性和速度/准确性之间进行权衡。

由于主题的范围,有许多事情我没有涉及,比如某些算法对 GPU 的使用。

我希望我能够分享我对这个迷人的话题的热情,并且你会发现它是有用的,并且我一如既往地欢迎任何建设性的反馈。

哈伯曼生存数据集探索性数据分析综合指南

原文:https://towardsdatascience.com/comprehensive-guide-to-exploratory-data-analysis-of-habermans-survival-data-set-b33f0373c83a?source=collection_archive---------18-----------------------

探索性数据分析

一个详尽和广泛的方法,处理探索性数据分析的每个方面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:https://pixabay.com/users/geralt-9301/(来自 Pixabay 的 Gerd Altmann)

探索性数据分析

**探索性数据分析(EDA)**是一个数据分析过程,主要目的是使用统计工具、绘图工具、线性代数和其他技术来挖掘隐藏在数据集中的信息。它有助于更好地理解数据,并突出其主要特征,这可能有助于做出对未来有影响的预测和预报。

理解数据是数据科学的核心。因此,EDA 对于生成精确的机器学习模型是必不可少的。考虑 Haberman 的生存数据集使用 Python 对其执行各种 EDA 过程。该数据集包含 1958 年至 1970 年间在芝加哥大学比林斯医院进行的一项关于乳腺癌手术患者存活率的研究。

数据集的各种属性是:

  1. 手术时患者的年龄(数字)
  2. 患者手术年份(1958 年至 1970 年之间的年份,数字)
  3. 检测到的阳性腋窝淋巴结数(数字)
  4. 生存状态(类属性)表示为:
  • 1 —如果患者存活了 5 年或更长时间
  • 2 —如果患者在 5 年内死亡

正如患者的医疗诊断在患者的治疗生命周期中起着关键作用一样,EDA 在数据评估和创建精确模型中也起着至关重要的作用。

导入必备的 Python 库

选择 Python 是因为它最好的 AI 包和机器学习库。在这里,我们导入执行数据分析和绘图所需的库:

  • 熊猫(Python 数据分析库)
  • Numpy(用于科学计算的 Python 包)
  • Matplotlib(Python 绘图库)
  • Seaborn(Python 统计数据可视化库)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

加载数据集

哈伯曼的生存数据集是一个逗号分隔值(csv)文件。 Pandasread_csv() 函数用于将 csv 文件(haberman.csv)读入名为 haberman 的数据帧中。Dataframe 是一个大小可变的二维结构,具有潜在的异构表格数据。

haberman = pd.read_csv('haberman.csv', header=0, names=['Age of Patient', 'Year of Operation', \                                                                          'Positive Axillary Nodes', 'Survival Status'])

一瞥数据集

让我们通过做一些初步的数据分析来熟悉数据集。首先,我们来看看数据集是什么样子的。在下面的代码片段中,iterrows()用于遍历 DataFrame 的每一行。

for i,j in haberman.iterrows():
    print(j)
    print()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

数据集的所有属性都是不言自明的。患者的年龄意味着患者的年龄。操作年份指执行操作的年份。阳性腋窝淋巴结表示患者中阳性腋窝淋巴结(淋巴结)的数量(存在或不存在)。阳性腋窝淋巴结是受癌细胞影响的淋巴结。最后,生存状态提供了关于患者 5 年或更长时间生存的信息。

观察:

  1. csv 文件包含 306 行和 4 列,这意味着数据集包含关于 306 名接受乳腺癌手术的患者的信息。考虑到数据量,数据集很小。
  2. 患者的诊断是基于患者表现出的症状。由于除了阳性腋窝淋巴结之外,没有其他数据集属性属于症状类别,我们可以假设阳性腋窝淋巴结的存在是乳腺癌的主要催化剂(原因)。根据 BreastCancer.org 的说法,为了去除浸润性乳腺癌,医生(在手术前或手术中)切除一个或多个腋下淋巴结,以便在显微镜下检查癌细胞。癌细胞的存在被称为淋巴结受累
  3. 在数据集中出现另一个症状会造成混乱,不知道在数据分析中应该优先考虑什么变量。因此在初步分析中,阳性腋窝淋巴结似乎是最重要的变量。

head()函数可以看到数据集的前五行。

haberman.head()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

现在让我们通过使用熊猫形状属性来查找数据点的总数和数据集的特征(属性)。数据点是属性或特征的集合。因此这是一个完整的记录。在给定的数据集中,数据点由涉及四个属性的数据组成(数据帧的行)。shape 属性返回表示数据帧维度的元组(数据帧将行数和列数存储为元组)。

print(haberman.shape)Output:
(306, 4)

数据集有 306 个数据点(行)和 4 个属性(列)。通过熊猫的列属性可以知道数据集的属性。

print(haberman.columns)Output:
Index(['Age of Patient', 'Year of Operation', 'Positive Axillary Nodes', 'Survival Status'],
      dtype='object')

这里的 dtype 指的是熊猫的数据类型。Pandas 中的对象数据类型相当于 Python 中的字符串数据类型。

生存状态属性(因变量)包含非分类类型的整数数据类型。因此需要转换成分类类型

haberman['Survival Status'] = haberman['Survival Status'].apply(
    lambda x: 'Survived' if x == 1 else 'Died')

让我们验证转换是否已经发生。

print(haberman.head(10))

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们现在可以看到,存活状态有标记为存活或死亡的字段。

info 方法可以显示数据集的简明摘要。这个方法打印关于数据帧的信息,包括索引数据类型和列数据类型、非空值和内存使用情况。

print(haberman.info())Output:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 306 entries, 0 to 305
Data columns (total 4 columns):
Age of Patient             306 non-null int64
Year of Operation          306 non-null int64
Positive Axillary Nodes    306 non-null int64
Survival Status            306 non-null object
dtypes: int64(3), object(1)
memory usage: 9.7+ KB
None

观察:

  1. 前三列即患者年龄、手术年份和腋窝淋巴结阳性的数据类型是整数。生存状态有一个对象数据类型。数据集有四个数据列。
  2. 数据集没有空值。
  3. 数据集使用的内存约为 9.7 KB

Pandas description方法生成描述性统计数据,其中包括汇总数据集分布的集中趋势、分散性和形状的信息,但不包括 NaN 值。它分析数字和对象序列,以及混合数据类型的 DataFrame 列集。根据所提供的内容,输出会有所不同。

print(haberman.describe(include='all'))Output:
Age of Patient  Year of Operation  Positive Axillary Nodes \
count       306.000000         306.000000               306.000000   
unique             NaN                NaN                      NaN   
top                NaN                NaN                      NaN   
freq               NaN                NaN                      NaN   
mean         52.457516          62.852941                 4.026144   
std          10.803452           3.249405                 7.189654   
min          30.000000          58.000000                 0.000000   
25%          44.000000          60.000000                 0.000000   
50%          52.000000          63.000000                 1.000000   
75%          60.750000          65.750000                 4.000000   
max          83.000000          69.000000                52.000000Survival Status  
count              306  
unique               2  
top           Survived  
freq               225  
mean               NaN  
std                NaN  
min                NaN  
25%                NaN  
50%                NaN  
75%                NaN  
max                NaN

观察:

  1. 该方法给出每个属性的总计数
  2. 对于数字数据(如患者年龄、手术年份、阳性腋窝淋巴结等变量),该方法提供了关于标准差(std)、平均值、百分位数(25%、50%和 75%)、最小值和最大值的有价值信息。第 50 百分位(50%)是中位数**。因此,获得了集中趋势(平均值和中值)和分散(标准偏差)的汇总。**
  3. 使用最小值和最大值,可以得出以下推论:
  • 患者的最大年龄为 83 岁,最小年龄为 30 岁。****
  • 经营年份从 58(1958)到 69(1969) 开始。
  • 一名或多名患者有 52 个阳性腋窝淋巴结,一名或多名患者有零个阳性腋窝淋巴结

4.对于对象数据类型(生存状态),结果将包括 unique,top 和 freq(频率)。数值数据类型的变量将在相应的字段中被赋予 NaN。生存状态有两个唯一值(存活和死亡)。顶部是最常见的值。因此幸存是最常见的生存状态。freq 是最常见的值的频率,这里的值是 225。所以存活下来的患者总数是 225

我们可以通过 value_counts()方法确定存活的患者总数。

print(haberman['Survival Status'].value_counts())Output:
Survived    225
Died         81
Name: Survival Status, dtype: int64

因此,我们可以得出这样的结论:从乳腺癌中幸存下来的患者比死于乳腺癌的患者多**(81)。因此,数据集是不平衡的**。****

目标

EDA 的主要目的是根据患者的年龄、手术年份和阳性腋窝淋巴结来确定患者是否能存活 5 年或更长时间。

不同层次的分析

现在让我们更深入地研究数据集。为此,必须考虑现有的不同层次的分析。它们是:

  • 单变量分析
  • 双变量分析
  • 多元分析

数据分析技术的选择最终取决于变量的数量、数据类型和统计调查的重点。

单变量分析

单变量分析是只处理一个变量的最简单的数据分析技术。作为一个单一的变量过程,它并没有给出因果关系的见解。单变量分析的主要目的是简单地描述数据,找出数据中的模式。正在考虑的单变量分析方法有:

  1. 一维散点图
  2. 概率密度函数
  3. 累积分布函数
  4. 箱形图
  5. 小提琴情节

双变量分析

双变量分析是建立两个变量之间相关性的过程。双变量分析比单变量分析更具分析性。如果数据似乎符合一条直线或曲线,那么这两个变量之间存在关系或相关性。正在考虑的双变量分析方法有:

  1. 二维散点图
  2. 配对图

多变量分析

多元分析是一种更复杂的统计分析。它是涉及三个或更多变量的分析,在需要了解它们之间关系的场景中实施。正在考虑的多变量分析方法是:

  1. 等值线图表

作案手法

分析将从双变量分析开始。将首先绘制二维散点图,并对其进行观察。然后我们将转到配对图,看看单个变量的分布和两个变量之间的关系。之后,将进行单变量和多变量分析。

二维散点图

二维散点图有助于使用笛卡尔坐标可视化两个变量之间的相关性。一个变量的值将沿 x 轴绘制,另一个变量的值将沿 y 轴绘制。数据将作为有序对(x,y)绘制在结果象限中,其中 x 与 x 轴上的值相关,y 与 y 轴上的值相关。

sns.set_style('whitegrid')
sns.FacetGrid(haberman, hue ='Survival Status', size = 8) \
.map(plt.scatter, 'Age of Patient', 'Positive Axillary Nodes') \
.add_legend()
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

FacetGrid 是一个多绘图网格,用于绘制条件关系。FacetGrid 对象将一个 DataFrame 作为输入,并将形成网格的行、列或色调维度的变量的名称作为输入。变量应该是分类的,变量的每个级别的数据将用于沿该轴的一个方面。map()方法负责在网格的每个空间上重复相同的绘图。它对每个方面的数据子集应用绘图功能。add_legend()方法创建绘图的图例。

观察:

  1. 30-40 岁年龄组的大多数患者已经从乳腺癌中存活下来,因为这里很少有橙色点。
  2. 非常罕见患者的阳性腋窝淋巴结超过 25 个(或者说 30 个)
  3. 没有阳性腋窝淋巴结时,几乎年龄组50-60 的所有患者都存活了**。我们可以假设在 50 和 60 之间没有橙色的点。**
  4. 所有 80 岁以上的患者都在手术后五年内死亡,因为这里没有蓝点。
  5. 少数具有较高数量的阳性腋窝淋巴结(大于 10)** 的患者也存活于乳腺癌(沿着阳性腋窝淋巴结出现蓝点> 10)。**

确定观察值:
我们可以通过在 haberman 数据帧上执行以下操作来确定 1 号观察值

df_3040 = haberman.loc[(haberman['Age of Patient']<=40) & (haberman['Age of Patient']>=30)]
#print(df_3040)df_3040_survived = df_3040.loc[df_3040['Survival Status']=='Survived']
print('No. of patients in the age group 30-40 survived: {0}' .format(len(df_3040_survived)))df_3040_died = df_3040.loc[df_3040['Survival Status']=='Died']
print('No. of patients in the age group 30-40 died: {0}' .format(len(df_3040_died)))Output:
No. of patients in the age group 30-40 survived: 39
No. of patients in the age group 30-40 died: 4

该输出验证了 1 号观察结果

我们可以通过以下方式确定 2 号观察值中提到的值 25(由 20 和 30 中点的蓝点表示)😗***

ax_node = haberman['Positive Axillary Nodes'].unique() #unique values of axillary node
ax_node.sort() #sorted the list
print(ax_node)Output:
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 28 30 35 46 52]

该列表的值为 25。所以我们可以安全地假设蓝点的值是 25。

我们可以通过以下操作确定4 号观察值**😗*

age = haberman['Age of Patient']
count = 0
print(len(age))
for i in age:
    if(i >= 80):
        count += 1
print('No. of patients whose age is greater than or equal to 80: {0}' .format(count))Output:
306
No. of patients whose age is greater than or equal to 80: 1

因此,只有一名患者的年龄大于或等于 80 岁。80 后的橙色圆点一定代表这个病人。

配对图

配对图绘制数据集中的成对关系。它将创建一个轴网格,这样数据中的每个数值变量将在 y 轴的一行中共享,在 x 轴的一列中共享。对角线轴不同于轴网格的其余部分,它们显示该列中变量数据的单变量分布。

sns.set_style('whitegrid')
sns.pairplot(haberman, hue = 'Survival Status', size = 4)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

在数据集中,有 3 个定量变量(患者年龄、手术年份、阳性腋窝淋巴结)和 1 个分类变量(生存状态)。只有数值(整数或浮点)连续值才会绘制在配对图中。因此,在上面显示的配对图中,我们有 3C2 图(即 3 个独特的图)。对角线两边有相等数量的图,它们是彼此的镜像。对角线图(图 1、图 5 和图 9) 展示了展示单个变量分布的直方图**。**

观察:

  1. 任何图中都没有线性分离
  2. 每个图中的数据有相当多的重叠。
  3. 图 2** 中,x 轴表示手术年份,y 轴表示患者年龄。有大量的重叠,很难根据剧情进行分类。观察到的一个有趣的事实是,在 1961 年和 1968 年期间接受手术的大多数患者已经存活了 5 年或更长时间(由于与其他年份相比橙色点非常少)。我们甚至可以把它重新表述为 1961 年和 1968 年接受乳腺癌手术的患者死亡人数最少**
  4. 图 3** 中,x 轴为阳性腋窝淋巴结,y 轴为患者年龄。即使有重叠的点,也有可区分的模式使我们能够做出推论。图 3(和图 7)似乎比其余的图更好(我们已经在二维散点图中深入分析了相同的图)。因此,阳性腋窝淋巴结和患者年龄是识别患者生存状态最有用的特征。**
  5. 图 6** 中,x 轴为阳性腋窝淋巴结,y 轴为手术年份。该图有个重叠最多的点。因此,它不会导致任何有意义的结论或分类。**
  6. 地块 4地块 2镜像图 7图 3 的镜像。图 8图 6 的镜像
  7. 最后,地块 7 和地块 3 是数据分析考虑的最佳地块

确定观测值:
我们可以通过对哈伯曼数据帧进行以下操作来确定地块 2 的3 号观测值:

df_1961 = haberman.loc[haberman['Year of Operation']==61]
df_1968 = haberman.loc[haberman['Year of Operation']==68]
#print(df_1961)df_1961_survived = df_1961.loc[df_1961['Survival Status']=='Survived']
print('No. of patients survived during 1961: {0}' .format(len(df_1961_survived)))df_1961_died = df_1961.loc[df_1961['Survival Status']=='Died']
print('No. of patients died during 1961: {0}' .format(len(df_1961_died)))aster = '*'
print(aster*45)df_1968_survived = df_1968.loc[df_1968['Survival Status']=='Survived']
print('No. of patients survived during 1968: {0}' .format(len(df_1968_survived)))df_1968_died = df_1968.loc[df_1968['Survival Status']=='Died']
print('No. of patients died during 1968: {0}' .format(len(df_1968_died)))Output:
No. of patients survived during 1961: 23
No. of patients died during 1961: 3
*********************************************
No. of patients survived during 1968: 10
No. of patients died during 1968: 3

一维散点图

使用单个变量进行推断的散点图是一维散点图。此处变量将位于 x 轴上,y 轴上的值为零(因为没有两个轴就无法绘图)。很明显,是单变量分析。

df_survived = haberman.loc[haberman['Survival Status'] == 'Survived'] 
df_died = haberman.loc[haberman['Survival Status'] == 'Died']
plt.plot(df_survived['Positive Axillary Nodes'], np.zeros_like(df_survived['Positive Axillary Nodes']), 'o')
plt.plot(df_died['Positive Axillary Nodes'], np.zeros_like(df_died['Positive Axillary Nodes']), 'o')
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

在上面的代码中, haberman.loc[ ] 用于从 haberman 数据帧中挑选与特定索引相关联的数据点,这些数据点又存储在另一个数据帧中。 np.zeros_like() 方法将创建一个零数组。“O”是字母 O 的小写字母,用于使图中的点更大且可见。

观察:

  1. 基于一个特征(阳性腋窝淋巴结)的一维散点图
  2. 数据有很大的重叠,这妨碍了进行任何有意义的观察。

柱状图

直方图是由卡尔·皮尔逊首先介绍的数字数据分布的精确表示。它给出了连续变量概率分布的估计。直方图是单变量分析,因为它只涉及一个变量。

构建直方图的第一步是“bin”(或桶)值的范围。“入库”意味着将整个值范围分成一系列区间,然后计算属于每个区间的值的数量。箱通常是变量的连续的、不重叠的区间。箱子必须相邻且大小相等(不要求)。除了最后一个箱子(最右边)外,所有箱子都是半开的。

对于相同大小的箱,在箱上方竖立一个矩形,其高度与每个箱中的病例数(频率或计数)成比例。

Matplotlib 用于绘制直方图,Numpy 用于计算计数和面元边缘。

import matplotlib.pyplot as plt
import numpy as npdf_axnodes = haberman['Positive Axillary Nodes'] #DataFrame of Positive Axillary Nodes
count, bin_edges = np.histogram(df_axnodes, bins=25)print('Bin Edges: ', bin_edges)
print('Counts per Bin: ', count)
plt.hist(df_axnodes, bins=25, color='skyblue', alpha=0.7) 
plt.xlabel('Positive Axillary Nodes', fontsize=15)
plt.ylabel('Frequency', fontsize=15)Output:
Bin Edges:  [ 0\.    2.08  4.16  6.24  8.32 10.4  12.48 14.56 16.64 18.72 20.8  22.88 24.96 27.04 29.12 31.2  33.28 35.36 37.44 39.52 41.6  43.68 45.76 47.84 49.92 52\.  ]
Counts per Bin:  [197  33  13  14   9   6   9   4   2   5   4   4   1   1   1   0   1   0   0   0   0   0   1   0   1]Text(0,0.5,'Frequency')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

Matplotlib 使用 plt.hist()绘制直方图,该函数将 DataFrame 作为输入。bin_edges 给出容器边缘(第一个容器的左边缘和最后一个容器的右边缘)。color 参数设置条形的颜色,alpha 参数设置条形的透明度。plt.xlabel 和 plt.y-label 分别用于设置 x 轴和 y 轴的标签。

观察结果

  • 306 名患者中的 197 名患者具有小于 2.08 的阳性腋窝淋巴结。因此大多数(64.37%)患者有少量阳性腋窝淋巴结。

概率密度函数

PDF 用于指定随机变量落在特定值范围内的概率。它是用来描述连续概率分布的概率函数。PDF 用于处理具有连续结果的随机变量的概率。从人群中任意选择一个人的身高就是一个典型的例子。

PDF 是直方图的平滑版本。直方图的平滑是使用核密度估计(KDE)来完成的。PDF(曲线)下的面积总和始终为 1。PDF 是单变量分析。

下面显示的代码片段将绘制 PDF。

基于患者年龄的 PDF

sns.set_style('whitegrid')
sns.FacetGrid(haberman, hue='Survival Status', size=8) \
    .map(sns.distplot, 'Age of Patient') \
    .add_legend()
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

条形(橙色和蓝色)是直方图,曲线代表 PDF。

观察:

  1. 数据有很大的重叠,相当于模糊不清
  2. 30-40 岁年龄段的患者比其他年龄段的患者有更多的生存机会。
  3. 40-60 岁年龄组的患者存活的可能性更小。
  4. 40-45 岁年龄组的死亡人数最多(存活的可能性最小)。
  5. 我们不能根据“患者年龄”这一属性对患者的生存机会做出最终结论。

基于运营年份的 PDF

sns.set_style('whitegrid')
sns.FacetGrid(haberman, hue='Survival Status', size=8) \
    .map(sns.distplot, 'Year of Operation') \
    .add_legend()
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

观察:

  1. 可以观察到大量重叠。
  2. 该图提供了关于成功手术(患者存活)和不成功手术的数量的信息。一次行动的成功不能以一年作为一个因素**。**
  3. 大多数不成功的手术都是在 1965 年进行的,然后是 1960 年。
  4. 最成功的手术是在 1961 年进行的。

基于阳性腋窝淋巴结的 PDF

sns.set_style('whitegrid')
sns.FacetGrid(haberman, hue='Survival Status', size=8) \
    .map(sns.distplot, 'Positive Axillary Nodes') \
    .add_legend()
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

观察:

  1. 阳性腋窝淋巴结(淋巴结受累)的存在可以是乳腺癌的明显表现。BreastCancer.org 在其网站上将它列为一个重要症状。因此,正腋淋巴结比其他属性更重要。
  2. 腋窝淋巴结阴性的患者比腋窝淋巴结阳性的患者有更高的存活几率
  3. 单个腋窝淋巴结阳性的患者也有很好的生存机会
  4. ****乳腺癌存活的可能性随着阳性腋窝淋巴结数量的增加而降低。
  5. 只有一小部分患者腋窝淋巴结阳性超过 25 个。
  6. ****阳性腋窝淋巴结是做数据分析的首选属性。

确定观察值:
我们可以通过对哈伯曼数据帧执行以下操作来确定观察值:

df_one = haberman.loc[haberman['Positive Axillary Nodes']<=1]
df_less = haberman.loc[(haberman['Positive Axillary Nodes']<=25) & (haberman['Positive Axillary Nodes']>1)]
df_more = haberman.loc[haberman['Positive Axillary Nodes']>25]df_one_survived = df_one.loc[df_one['Survival Status']=='Survived']
print('No. of patients survived(with one or no positive nodes): {0}' .format(len(df_one_survived)))df_one_died = df_one.loc[df_one['Survival Status']=='Died']
print('No. of patients died(with one or no positive nodes): {0}' .format(len(df_one_died)))aster = '*'
print(aster*65)df_less_survived = df_less.loc[df_less['Survival Status']=='Survived']
print('No. of patients survived(1<positive nodes<=25): {0}' .format(len(df_less_survived)))df_less_died = df_less.loc[df_less['Survival Status']=='Died']
print('No. of patients died(1<positive nodes<=25): {0}' .format(len(df_less_died)))print(aster*65)df_more_survived = df_more.loc[df_more['Survival Status']=='Survived']
print('No. of patients survived(25<positive nodes<=52): {0}' .format(len(df_more_survived)))df_more_died = df_more.loc[df_more['Survival Status']=='Died']
print('No. of patients died(25<positive nodes<=52): {0}' .format(len(df_more_died)))Output:
No. of patients survived(with one or no positive nodes): 150
No. of patients died(with one or no positive nodes): 27
*****************************************************************
No. of patients survived(1<positive nodes<=25): 72
No. of patients died(1<positive nodes<=25): 52
*****************************************************************
No. of patients survived(25<positive nodes<=52): 3
No. of patients died(25<positive nodes<=52): 2

输出已经确定了观察值。我们可以得出以下结论:

  1. **有**个或零个腋窝淋巴结阳性的患者中,有 85%乳腺癌存活。
  2. 腋窝淋巴结阳性小于 25 且大于 1 的患者中有 58%存活了 5 年或更长时间
  3. 腋窝淋巴结阳性大于 25 的患者,有 60%的乳腺癌存活。
  4. 这些统计数据证明,如果阳性腋窝淋巴结的数目是 1 或 0,患者的存活机会是相当高的。如果这个数字大于 1,那么存活几率在 58%到 60%之间。

累积分布函数

实值随机变量 X 的累积分布函数(CDF)是该变量取值小于或等于 X 的概率。
F(x) = P(X < = x)
其中右手边代表随机变量 X 取值小于或等于 X 的概率。X 位于半闭区间(a,b)的概率,其中 a < b 因此:
P(a < X <

概率密度函数的积分给出了 CDF。CDF 也是单变量分析。

使用选择的变量“阳性腋窝淋巴结”绘制 CDF。

**df_axnodes_survived = haberman.loc[haberman['Survival Status']=='Survived']
counts1, bin_edges1 = np.histogram(df_axnodes_survived['Positive Axillary Nodes'], bins=10, density=True)
pdf1 = counts1/(sum(counts1))
print('PDF of patients survived 5 years or longer:', pdf1)
print('Bin Edges: ', bin_edges1)
cdf1 = np.cumsum(pdf1)aster = '*'
print(aster * 60)df_axnodes_died = haberman.loc[haberman['Survival Status']=='Died']
counts2, bin_edges2 = np.histogram(df_axnodes_died['Positive Axillary Nodes'], bins=10, density=True)
pdf2 = counts2/(sum(counts2))
print('PDF of patients died within 5 years:', pdf2)
print('Bin Edges: ', bin_edges2)
cdf2 = np.cumsum(pdf2)print(aster * 60)line1, = plt.plot(bin_edges1[1:], pdf1, label='PDF_Survived')
line2, = plt.plot(bin_edges1[1:], cdf1, label='CDF_Survived')
line3, = plt.plot(bin_edges2[1:], pdf2, label='PDF_Died')
line4, = plt.plot(bin_edges2[1:], cdf2, label='CDF_Died')
plt.legend(handles=[line1, line2, line3, line4])plt.xlabel('Positive Axillary Nodes', fontsize=15)
plt.show()Output:
PDF of patients survived 5 years or longer: [0.83555556 0.08       0.02222222 0.02666667 0.01777778 0.00444444 0.00888889 0\.         0\.         0.00444444]
Bin Edges:  [ 0\.   4.6  9.2 13.8 18.4 23\.  27.6 32.2 36.8 41.4 46\. ]
************************************************************
PDF of patients died within 5 years: [0.56790123 0.14814815 0.13580247 0.04938272 0.07407407 0\. 0.01234568 0\.         0\.         0.01234568]
Bin Edges:  [ 0\.   5.2 10.4 15.6 20.8 26\.  31.2 36.4 41.6 46.8 52\. ]
**************************************************************

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

Matplotlib 用于绘制直方图,Numpy 用于计算计数和 bin 边缘。Matplotlib 使用 plt.hist()绘制直方图,该函数将 DataFrame 作为输入。bin_edges 给出容器边缘(第一个容器的左边缘和最后一个容器的右边缘)。np.cumsum()是一个计算累积和的 numpy 方法。plt.legend()是一个 Matplotlib 方法,用于生成图形的图例。plt.xlabel()是另一个标记 x 轴的 Matplotlib 方法

观察:

  1. 甚至有更多阳性腋窝淋巴结的患者也能从乳腺癌中存活下来。与此相反,没有阳性腋窝淋巴结的患者在接受手术后死亡。
  2. 癌症存活患者腋窝淋巴结阳性的最大数量为 46 个
  3. 83.55% 癌症存活患者腋窝淋巴结在0 ~ 4.6范围内阳性。****
  4. 死亡患者中有 56.79% 的腋窝淋巴结在0 ~ 5.2范围内。****

确定观察值:
我们可以通过对哈伯曼数据帧执行以下操作来确定 1 号观察值:

**df_axnodes_died = haberman.loc[haberman['Survival Status']=='Died']
df_no_axnodes_died = df_axnodes_died.loc[df_axnodes_died['Positive Axillary Nodes']==0]
print('No. of patients died with zero Positive Axillary Node: ', len(df_no_axnodes_died))df_axnodes_survived = haberman.loc[haberman['Survival Status']=='Survived']
df_high_axnodes_survived = df_axnodes_survived.loc[df_axnodes_survived['Positive Axillary Nodes']>=20]
print('No. of patients survived with high Positive Axillary Nodes(>=20): ', len(df_high_axnodes_survived))Output:
No. of patients died with zero Positive Axillary Node:  19
No. of patients survived with high Positive Axillary Nodes(>=20):  7
     Age of Patient  Year of Operation  Positive Axillary Nodes  \
7                34                 59                        0   
34               39                 66                        0   
44               41                 64                        0   
45               41                 67                        0   
54               42                 59                        0   
64               43                 64                        0   
65               43                 64                        0   
81               45                 66                        0   
97               47                 62                        0   
98               47                 65                        0   
114              49                 63                        0   
125              50                 64                        0   
224              60                 65                        0   
230              61                 65                        0   
239              62                 58                        0   
258              65                 58                        0   
268              66                 58                        0   
285              70                 58                        0   
293              72                 63                        0Survival Status  
7              Died  
34             Died  
44             Died  
45             Died  
54             Died  
64             Died  
65             Died  
81             Died  
97             Died  
98             Died  
114            Died  
125            Died  
224            Died  
230            Died  
239            Died  
258            Died  
268            Died  
285            Died  
293            Died  
     Age of Patient  Year of Operation  Positive Axillary Nodes  \
9                34                 58                       30   
59               42                 62                       20   
174              54                 67                       46   
188              55                 69                       22   
227              60                 61                       25   
252              63                 61                       28   
254              64                 65                       22Survival Status  
9          Survived  
59         Survived  
174        Survived  
188        Survived  
227        Survived  
252        Survived  
254        Survived**

箱形图

箱线图是基于五个数字汇总的数据分布的直观表示。这五个数字是最小或最小的数字、第一个四分位数(Q1)或第 25 个百分位数、中间值(Q2)或第 50 个百分位数、第三个四分位数(Q3)或第 75 个百分位数以及最大或最大的数字。Q1 是最小值和中间值之间的中间数。Q3 是中间值和最大值之间的中间值。四分位数间距(IQR)是第一个四分位数和第三个四分位数之间的差值。
IQR = Q3 — Q2
方框图的高度代表 IQR。方框的顶线和底线分别代表第一个四分位数和第三个四分位数。盒子顶线和底线之间的线代表中间值。从方框平行延伸的线被称为“胡须”,用于指示上下四分位数之外的可变性。异常值有时被绘制成与须状物成直线的单个点。异常值是与其他观察值显著不同的数据点。它不属于总的分配模式。

盒须图是数学家约翰·图基在 1969 年首次提出的。箱线图可以垂直或水平绘制。虽然与直方图或密度图相比,箱线图可能看起来很原始,但它们具有占用空间少的优点,这在比较许多组或数据集之间的分布时很有用。

Python 统计数据可视化库 Seaborn 用于绘制箱线图

**sns.boxplot(x='Survival Status', y='Positive Axillary Nodes', data=haberman)
plt.show()**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

小提琴情节

小提琴图用于可视化数据的分布及其概率密度。它是箱形图与两侧的旋转核密度图的组合,用于显示数据的分布形状。中间的白点是中间值,中间粗黑条代表四分位数范围。从它延伸出来的黑色细线代表数据中的最大值和最小值。小提琴图类似于箱线图,只是它们也显示不同值的数据的概率密度,通常由核密度估计器平滑。结合两个世界最好的(直方图和 PDF,方框图)给出了小提琴图。小提琴剧情是单变量分析。****

**sns.violinplot(x='Survival Status', y='Positive Axillary Nodes', data=haberman, size=8)
plt.show()**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

观察:

  1. IQR 是衡量谎言价值的标准。因此,幸存于的患者的阳性腋窝淋巴结少于 3 个。同样,死亡的患者,腋窝淋巴结阳性大于 2 个****
  2. 晶须外点的存在表明异常值的存在。存活类别(存活 5 年或更长时间的患者)中异常值的数量大大高于死亡类别(5 年内死亡的患者)。****
  3. 幸存类别的 Q1 和中位数几乎相同。死亡类别的中位数和幸存类别的 Q3 显然在同一条线上。因此存在重叠**,这可能导致至少 15%到 20% 的误差。因此,很难设定一个阈值来区分患者的生存机会。**
  4. 大多数没有阳性腋窝淋巴结的患者在乳腺癌中存活。类似地,大多数有大量阳性腋窝淋巴结的患者死亡。
  5. 每条规则都有例外。它也适用于这里。因为具有大量阳性腋窝淋巴结的患者很少存活,并且没有阳性腋窝淋巴结的患者很少死亡。

等值线图表

等值线图是一种多变量分析。等高线图不是一种标准化技术,而是一种通过绘制二维格式的称为等高线的常量 z 切片来表示三维表面的图形技术。Seaborn 习惯于绘制等高线图。****

sns.jointplot(x='Age of Patient', y='Positive Axillary Nodes', data=haberman, kind='kde')
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片提供:iambipin

观察:

  1. 在所有腋窝淋巴结阳性少于或等于 2 个的患者中,大多数患者的年龄在50–56 岁之间。****

确定观察值:
我们可以通过对哈伯曼数据帧执行以下操作来确定观察值:

df_axnodes_zero = haberman.loc[haberman['Positive Axillary Nodes']<=2]
print('No. of patients with positive axillary nodes<=2: ', len(df_axnodes_zero))
df_axnodes_zero_50 = df_axnodes_zero.loc[(df_axnodes_zero['Age of Patient']>=50) & (df_axnodes_zero['Age of Patient']<=56)]
print('No. of patients in the age group 50-56 who have positive axillary nodes<=2: ', len(df_axnodes_zero_50))Output:
No. of patients with positive axillary nodes<=2:  197
No. of patients in the age group 50-56 who have positive axillary nodes<=2:  40

因此,在 50-56 岁年龄组中,20.30%腋窝淋巴结阳性小于或等于两个淋巴结的患者。

让我们总结一下我们在探索性数据分析过程中所做的重要观察。

结论:

  1. 30-40 岁年龄段的大多数患者都从乳腺癌中幸存下来。
  2. 在 1961 年至 1968 年期间接受手术的大多数患者在手术后存活了 5 年或更长时间。
  3. 出现阳性腋窝淋巴结(淋巴结受累)可以是乳腺癌的明显表现。一般来说,乳腺癌患者的生存机会与阳性腋窝淋巴结的数量成反比。
  4. 腋窝淋巴结无阳性的患者比腋窝淋巴结有阳性的患者有更高的存活几率
  5. 少数有大量阳性腋窝淋巴结的患者存活**,少数无阳性腋窝淋巴结的患者死亡。所以没有阳性腋窝淋巴结并不能预示着绝对的生存保证。**
  6. 只有一小部分患者腋窝淋巴结阳性超过 25 个**。**
  7. 所以基于探索性数据分析,我们可以提出一个关于乳腺癌患者生存几率的假设。

参考文献:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值