Python 应用计算思维(四)
原文:
zh.annas-archive.org/md5/D0EC374B077B4C90AC07697977B27176
译者:飞龙
第十四章:在统计分析中使用计算思维和 Python
在本章中,我们将使用 Python 和计算思维的元素来解决需要统计分析算法的问题。我们将使用pandas DataFrames在 Python 环境中创建统计分析算法。在 Python 中还需要其他软件包来创建统计分析,例如NumPy、pytz等。当我们需要处理的代码和学习这些库帮助我们做什么时,我们将使用这些软件包,比如使用 pandas 整理数据。
在本章中,我们将涵盖以下主题:
-
定义问题和 Python 数据选择
-
数据预处理
-
使用可视化处理、分析和总结数据
在本章结束时,您将能够设计最适合您所面临情况的算法。您还将能够确定与所提出问题最符合的 Python 函数,并概括您的解决方案。
技术要求
您需要最新版本的 Python 来运行本章的代码。
在本章的问题中,您需要安装pandas、NumPy、SciPy和Scikit-Learn软件包。
您可以在本章中找到使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter14
定义问题和 Python 数据选择
在我们看 pandas 库之前,让我们定义一下数据分析是什么。当我们谈论数据分析时,我们指的是检查、清洗、转换和建模数据的过程,目的是发现有用的数据,通知结论,并支持决策。决策是至关重要的。我们不只是想看看数据在过去发生了什么。我们希望利用数据来为未来做出知情决策。
看一下数据分析的一些用途:
-
商业:它有助于基于客户趋势和行为预测做出决策,提高业务生产力,并推动有效的决策。
-
天气预报:收集和分析大气数据(温度、湿度、风力等)以了解大气过程(气象学),从而确定大气将来的演变。
-
交通:数据可用于确定趋势,包括交通、事故等,帮助我们做出关于交通模式、交通灯持续时间等的决策。
上述用途只是可能应用的一部分。数据分析被用于非常广泛的事情,由企业和教育组织做出关键决策,为社区提供资源,资助我们的学校和大学,以及更多。
因此,让我们看看我们有哪些工具可用于分析数据。在 Python 中用于数据分析的主要库之一是 pandas 软件包。pandas 的伟大之处在于其易用性、简单的数据结构和高性能。使用 pandas 简化了我们在数据分析中的工作。
定义 pandas
重要的一点是 pandas 是建立在 NumPy 之上的。NumPy是一个帮助我们处理数组的软件包。Python 本身并没有数组,因此这些软件包允许我们创建、使用数组,然后在此基础上构建。
Pandas 提供了一种灵活且简单的数据结构,可以简化您在数据分析中的工作。它是处理大数据的强大工具。当我们谈论大数据时,我们指的是结构化和非结构化数据集,这些数据集经过分析,以便我们可以获得更好的见解,并帮助业务和组织制定决策或策略。pandas 可以处理导入不同格式的数据,如.csv
文件、SQL和JSON,以及各种操作,如选择数据、合并、重塑和清理数据。
在 pandas 中有两种不同的存储数据的方式——系列和数据框:
-
Series是一维数组,可以容纳任何数据类型(整数、字符串或浮点数);系列代表一列数据。
-
数据框是可以具有多个列和数据类型的二维对象。它接受诸如字典、系列、列表和其他数据框的输入。
现在让我们学习何时使用 pandas。
确定何时使用 pandas
总的来说,Pandas 在一般情况下非常有用,但当我们处理大量数据并使用逗号分隔值(CSV)文件时,它真的是一个很好的工具。这些文件被存储为表格,如电子表格。另一件事是我们可以在 pandas 中建立块。是的,在 pandas 中有一个chunksize
参数,可以帮助我们分解数据。假设我们有 500 万行。我们可以决定使用chunksize
将其分解为 100 万行。
此外,有时我们有大量的数据文件,但只想查看其中的一些组件。Pandas 允许我们标识要包含的列和要忽略的列。
处理 pandas 系列
如前一节所述——定义 pandas——系列是一维的。我们可以使用一些简单的代码创建一个空的 pandas 系列。请注意,通常在使用包和库时,我们首先导入 pandas 库,如下所示:
import pandas as pd
demo_series = pd.Series()
print(demo_series)
默认创建的系列将具有float
类型,因为我们在算法中没有建立其他类型。然而,控制台打印了一个警告,即在将来,空系列的dtypes将被设置为对象,而不是float
。dtype
代表数据类型;在这种情况下,它是一个浮点数。看一下以下截图,显示了我们运行算法时的输出:
图 14.1 – 在 pandas 中创建空系列时未识别 dtype 时的输出
正如您所看到的,这里实际上并没有错误,只是关于数据存储方式的警告。
现在,让我们创建一个具有定义元素的系列。为此,我们需要导入 pandas 和numpy
,以便我们可以创建数组,然后创建系列。看一下以下代码片段:
ch14_seriesDemo.py
import pandas as pd
import numpy as np
dataset = np.array(['yellow', 'green', 'blue', 'violet', 'red'])
data_series = pd.Series(dataset)
print(data_series)
正如您从前面的代码中看到的,我们使用numpy
存储了我们的数组,然后使用该数组创建了一个系列。最后,我们打印了系列。输出是一个表格,如下所示:
0 yellow
1 green
2 blue
3 violet
4 red
dtype: object
我们也可以通过首先创建一个列表来获得完全相同的结果。看一下这段代码:
ch14_seriesDemo2.py
import pandas as pd
import numpy as np
myList = ['yellow', 'green', 'blue', 'violet', 'red']
data_series = pd.Series(myList)
print(data_series)
请注意,我们直接从列表中创建了系列。我们不打算展示这段代码的输出,因为它与上一段代码的输出完全相同。
我们也可以从字典中获得一个系列。让我们看一下以下代码片段:
ch14_seriesDemo3.py
import pandas as pd
myDictionary = {
'Name' : 'Miguel',
'Number' : 42,
'Age' : 'unknown'
}
mySeries = pd.Series(myDictionary)
print(mySeries)
前面的代码只是一个演示,但当我们运行算法时,我们确实得到了一个包含字典值的表系列。让我们看一下输出:
Name Miguel
Number 42
Age unknown
dtype: object
正如您所看到的,我们有基于键值对的两列,以及类型为object
。一个重要的一点是系列没有列标题。为此,我们需要使用数据框。
如果我们想要访问系列中的特定元素怎么办? 好吧,要访问第一个元素,让我们使用以下代码片段:
ch14_demo4.py
import pandas as pd
myDictionary = {
'Name' : 'Miguel',
'Number' : 42,
'Age' : 'unknown'
}
mySeries = pd.Series(myDictionary)
print(mySeries[0])
前面代码的输出只是Miguel
。这是因为我们使用索引 0 来标识我们从字典中想要的内容,因此它将为我们提供第一个键值对的值。如果我们想要前两个键值对元素的值对,我们将(mySeries[0])
替换为(mySeries[:2])
。然后,输出将如下所示:
Name Miguel
Number 42
dtype: object
系列还有许多其他事情可以做,因此可以尝试使用索引和使用列表、字典或 NumPy 数组创建不同类型的系列。现在,让我们继续学习数据框。
使用 pandas 数据框
现在,让我们来看看如何处理数据框。首先,让我们来看一个带有 pandas 的.csv
文件。我们将在接下来的代码片段中使用demo.csv
文件。请将文件的位置替换为您保存文件的位置,该位置可以在 GitHub 存储库中找到:
ch14_csvDemo.py
import pandas as pd
data_set = pd.read_csv('C:\\...\\demo.csv')
data_set.head()
前面的代码做了三件事。它导入了 pandas 包,以便我们可以使用我们需要的数据功能,它告诉程序打开我们将要使用的数据文件,然后它给出了文件中的前几行数据,以便我们可以看到我们正在处理的内容。以下截图显示了前面代码的结果或输出:
图 14.2 - 显示数据集的前几行输出
从前面的截图中可以看出,表格未显示文件中包含的所有值。虽然我们的文件不是充满大数据,但它确实包含更多的信息行。这只是让我们预览我们的数据的一种方式。
但这是一个干净的数据集。如果我们有一个数据集的行或列缺少信息会发生什么? 好吧,pandas 允许我们处理文件以准备好使用。数据框还可以准备我们的数据,以便我们可以创建可视化表示。这些可视化或图表将允许我们看到趋势,进行预测,确定我们可以用于训练的值,等等。数据框实际上只是我们可以用数据集做的其他一切的基础。
在这一部分,我们学习了问题,如何使用 pandas 以及 pandas 系列和数据框的一些功能。
作为一种注意,在本章的其余部分,我们将更加专注于数据框而不是系列。但在进入应用程序之前,让我们看看如何通过预处理数据框来避免错误和陷阱。
数据预处理
预处理数据是一种将原始数据转换为可用和高效格式的技术。实际上,这是数据挖掘和机器学习过程中最重要的步骤。
当我们预处理数据时,我们实际上是在清理它、转换它或进行数据减少。在本节中,我们将看看这些都意味着什么。
数据清理
数据清理指的是使我们的数据集更高效的过程。如果我们在真正大型的数据集中进行数据清理,我们可以加快算法,避免错误,并获得更好的结果。在数据清理时,我们处理两件事情:
-
缺失数据:这可以通过忽略数据或手动输入缺失数据的值来解决。
-
嘈杂的数据:这可以通过使用分箱、回归或聚类等其他过程来修复/改进。
我们将更详细地看看这些事情。
处理缺失数据
让我们看看我们如何处理缺失数据。首先,我们将学习如何忽略缺失数据。我们可以使用 pandas 找到具有缺失值的行。当我们这样做时,我们正在清理我们的数据集。现在,我们不会介绍我们可以使用的每种方法,只介绍一种可以去除具有缺失值的行的方法。与往常一样,使用的数据集可在我们的 GitHub 存储库中找到,并且您需要更新文件位置。让我们看看以下代码片段:
ch14_cleaningDemo1.py
import pandas as pd
myData = pd.read_csv('C:\\...\\demo_missing.csv')
print(myData)
cleanData = myData.dropna(axis = 0, how = 'any')
print(cleanData)
在前面的代码中,第一个print
语句是为了我们自己看到我们的数据集是什么样子的。*您绝对不会想在大文件中这样做!*以下截图显示了第一个print
输出:
图 14.3 - 第一个打印语句,原始数据集
请注意,1,Blue列在Countries下有一个NaN值,下一列(2,Yellow)在Numbers列下有一个缺失值。当我们使用dropna()
时,算法将删除具有缺失值的行。以下截图显示了带有修改后数据集的打印语句:
图 14.4 - 打印干净的数据集
从前面的截图中可以看到,缺少值的两行在我们的新数据集中被消除了。现在,我们可以对这些数据运行任何我们想要的分析。
如果您只想检查一个列以验证是否存在缺失值,您可以使用以下代码片段:
ch14_cleaningDemo2.py
import pandas as pd
myData = pd.read_csv('C:\\...\\demo_missing.csv')
print(pd.isna(myData['Countries']))
请注意,在前面的算法中,我们使用Countries
列标题来验证特定列。当我们运行算法时,我们的输出如下:
0 False
1 True
2 False
3 False
4 False
5 False
6 False
7 False
8 False
9 False
Name: Countries, dtype: bool
如您所见,我们的数据集中的第二行在Countries
列中有一个缺失值。
虽然我们不会详细介绍每种方法,但您也可以删除列。您可以选择删除具有一定数量缺失值的行和/或列。例如,您可以选择仅删除具有两个以上缺失值的行或列。如果这样做,您仍然需要担心可能仍然缺少值的列或行,因为只有一个缺失值而未被删除。对于这些情况,您可以选择用其他值替换缺失值。
假设您想要根据列替换一个值。要这样做,让我们看一下以下代码片段:
ch14_cleaningDemo3.py
import pandas as pd
myData = pd.read_csv('C:\\...\\demo_missing.csv')
print(myData.fillna(0))
从前面的代码中可以注意到,我们正在用值0
填充每个空单元格。当我们运行算法时,我们得到以下输出:
图 14.5 - 替换缺失值
请注意前面截图中突出显示的值。这些是我们的算法替换的值。现在,让我们看一下我们如何处理嘈杂数据。
处理嘈杂数据
首先,让我们定义一下嘈杂数据的含义。当我们有大量数据,其中一些对我们的分析没有用处时,我们称之为嘈杂数据。嘈杂数据也用于指代数据损坏。实际上,它只是无用的数据。
我们处理嘈杂数据的三种方法是分箱、回归和聚类:
-
分箱使用相邻数据来平滑排序后的数据值。排序后的值放入箱中,这些箱是算法内创建的组。
-
聚类方法识别并移除数据集中的异常值。
-
回归方法通过将数据拟合到回归函数中来平滑数据。
分箱的目的是减少一些错误。在分箱中,数据被分成小的桶或箱。然后用计算出的箱值替换数据。当我们进行分箱过程时,我们正在平滑数据。
以下是算法中一个简单的数值数据集的示例。以下代码片段将创建具有相等频率的箱:
ch14_binning1.py
#Binning with equal frequency
def equal_frequency(array1, m):
l = len(array1)
n = int(l / m)
for i in range(0, m):
array = []
for j in range(i * n, (i + 1) * n):
if j >= l:
break
array = array + [array1[j]]
print(array)
#Input dataset
dataset = [3, 6, 7, 9, 11, 14, 10, 15, 19, 35, 38, 45, 48, 49, 76]
#Input number of bins
m = 5
print("Equal Frequency Binning: ")
equal_frequency(dataset, m)
在查看前面的代码时,您可以看到箱的数量被定义为5
,因此数据将被分成五个列表。分箱实际上是一种分组信息的方法。我们告诉算法我们想要这样做以及我们想要多少个箱,它就会提供这些箱或组中的数据。在这种情况下,我们得到了这五个列表。看一下输出:
Equal Frequency Binning:
[3, 6, 7]
[9, 11, 14]
[10, 15, 19]
[35, 38, 45]
[48, 49, 76]
如您所见,算法创建了五个箱,每个箱中有三个值。
重要提示:
请注意,分箱过程并不为我们组织数据。因此,如果我们重新排序我们的值,它们仍然会按照数据输入的顺序进行分箱。
现在,让我们看一个使用等宽度的分箱算法:
ch14_binning2.py
#Binning with equal width
def equal_width(array1, m):
w = int((max(array1) - min(array1)) / m)
min1 = min(array1)
array = []
for i in range(0, m + 1):
array = array + [min1 + w * i]
arrayi=[]
for i in range(0, m):
result = []
for j in array1:
if j >= array[i] and j <= array[i+1]:
result += [j]
arrayi += [result]
print(arrayi)
#Input dataset
dataset = [3, 6, 7, 9, 11, 14, 10, 15, 19, 35, 38, 45, 48, 49, 76, 81, 208, 221]
#Input number of bins
m = 3
print("\nEqual Width Binning:")
equal_width(dataset, m)
上面代码片段将我们的数据分成了三个箱。等宽度分箱的目标是将数据集分成相等大小的箱,这在等宽度分箱的情况下意味着相等的范围。数据将被分割,但重要的是要注意,我们在这里谈论的是范围,因此对于这个特定数据集,这些箱中的元素数量不会相同。上面代码片段的输出如下:
Equal Width Binning:
[[3, 6, 7, 9, 11, 14, 10, 15, 19, 35, 38, 45, 48, 49], [76, 81], [208]]
正如你所看到的,分箱产生的输出看起来并不像等频输出那样干净,但实际上更受欢迎。
现在,让我们谈谈数据转换。
转换数据
Pandas 允许我们转换数据。以下是一些我们可以转换数据的方法:
-
归一化将值转换为新的范围;最流行的是最小-最大归一化,如下所示:
-
属性选择是通过用不同的属性或属性替换属性来转换数据的过程。
-
概念层次结构实际上是通过减少数据来进行的转换。它是通过用更高级的概念(如短、长、极长)替换数字(10、15、40)等概念来完成的。
在下一节中,我们将浏览数据的减少。
减少数据
数据减少是指一种过程,允许我们从数据集中减少数据的表示,但只有在减少数据的体积后才能获得类似甚至相同的结果。
我们不会在这里深入讨论所有概念,因为在示例中查看它们会更容易,但以下是一些数据减少的方法:
-
从数据集中删除无效数据
-
为不同级别的数据创建汇总
将无效数据移除可以看作是处理任何异常值。数据可能已被错误输入,条件可能不是最佳的,或者类似情况。当我们有一个数据点与整个数据集不符合时,特别是当我们有大量数据点进行比较时,我们可以将该数据点作为无效数据或异常值移除。
在创建不同级别的汇总时,我们对数据集进行聚合,并在每个级别进行测试和生成汇总。假设你有 100 个数据点(数据集通常会有数千个,但用较小的数字更容易解释)。我们可以为前 20 个数据点创建一个汇总。然后,我们可以对前 40 个数据点做同样的操作,然后是前 60 个,依此类推。通过比较,我们可以看到趋势,并利用数据集的这些较小子部分来进行预测,如果趋势成立的话。这就是数据减少的作用,简化数据集的同时仍然获得准确的结果。
在这一部分,我们学习了如何处理需要清理的数据。我们学习了一些清理数据的方法,比如消除缺失数据或替换缺失点。我们还了解了嘈杂数据以及如何解决数据集中的嘈杂数据问题。最后,我们学习了数据减少以及如何通过删除无效数据和创建数据的汇总来获得准确的结果。
这只是我们处理数据时所做的一些类型的简介。所以,让我们看一个例子,以便我们能把其中一些放入上下文中。
使用可视化处理、分析和汇总数据
我们现在在房地产领域工作,因为我们想做得好,我们真的想建立一个帮助我们分析数据并预测房价的算法。但是让我们再想一想。我们可以广义地或狭义地定义这个问题。我们可以对一个州的所有房屋或一个社区中三个或更多卧室的房屋进行定价分析。进行分析重要吗?也许。但这难道不是我们想要研究这个问题的原因吗?
让我们先来看看我们如何处理数据。
处理数据
让我们先收集一些数据。对于这个问题,我们使用的是kv_house_data.csv
数据集,它可以在我们的 GitHub 存储库中找到。要查看这个数据集,我们需要很多库。我们主要谈论的是 Pandas,是的,但我们还想进行可视化和一些分析,所以我们还需要Seaborn、SciPy和Scikit-Learn。完整的算法可以在ch14_housePrice_prediction.py
文件中找到。我们将逐步查看它,讨论我们在进行过程中所做的事情:
ch14_housePrice_prediction.py
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
*那么这些库都是什么?*我们知道 Pandas。我们使用它来组织我们的数据。NumPy 帮助我们处理数组。Seaborn 和 Matplotlib 都用于可视化。如果我们要进一步创建模型并使用数据集进行训练,我们还需要 Scikit-Learn。对于这个例子,我们将坚持在训练之前获得一些图表。
现在,让我们导入我们的数据集。还记得第十二章中提到的吗,在实验和数据分析问题中使用 Python,您可以直接设置文件的目录。您也可以提供数据文件的完整路径,就像我们在本章中之前做过的那样,比如在清理演示中。您可以使用os.chdir()
来建立目录,将文件的位置添加到括号中,然后使用以下代码片段来读取.csv
文件:
housing_data= pd.read_csv("kc_house_data.csv")
我们在这里使用了一个 pandas 函数。看到那个 pd.read_csv()
了吗?那个pd
就是 pandas,因为我们将 pandas 导入为pd
,而read_csv()
是允许算法获取文件中信息的函数。
如果您忘记输入您的目录或者包含错误的位置,您将会收到一个错误代码,如下面的截图所示:
图 14.6 - 文件路径错误
正如您所看到的,Python 会确保您知道自己犯了一个错误。有时这可能会让人恼火,但肯定是有帮助的。
现在我们有了我们的数据,我们需要检查并清理它。这就是我们之前在本章中分享的所有内容发挥作用的地方。让我们看看实际情况是什么样子。
分析和总结数据
现在,有一件事我们并没有谈论太多,那就是 Python 的变量资源管理器。更具体地说,是Spyder 的 Python 变量资源管理器。Spyder 是一个集成环境,是免费的。它可以与 Python 一起工作,像平常一样运行 Python,但也为我们提供了更好的编辑工具。下面的截图显示了当您从 Python 的变量资源管理器中导入数据集时应该看起来的样子。当我们在 Spyder 中运行ch14_housePrice_prediction.py
Python 算法时,我们可以在变量资源管理器中看到我们的变量。下面的截图显示了当我们运行这个算法时从变量资源管理器中获取的数据:
图 14.7 - Spyder 中的变量资源管理器视图
当我们处理大量数据和更大的算法时,这个工具变得非常关键。我们可以从这个工具中获得很多信息。例如,让我们看看housing_data
变量。在图 14.7中,您可以看到我们算法中这个变量的类型是DataFrame,大小为**(21613, 21)**。
如果你在变量资源管理器中双击变量,你会得到以下截图中显示的内容(请注意,截图可能会因你使用的环境而有所不同。在使用 Spyder 或 Jupyter 等环境运行此代码时,取决于你的主题设置和选择,表格可能会有所不同,有不同的颜色方案或没有颜色方案):
图 14.8 - Spyder 中的 DataFrame 变量视图
这只是获取 DataFrame 的一些信息的一种方式。Spyder 还允许我们调整窗口大小,这样我们可以查看更多列,滚动查找值等。如果我们在 Python 控制台中,这并不容易。你可以获取信息,只是不那么容易。
以下是可以为我们提供一些信息的代码:
housing_data.head()
上面的代码将显示我们数据集的前五行。看一下以下截图;在价格和经度之间有省略号(…)。这是因为 Python 想让我们知道这两者之间还有其他列:
图 14.9 - DataFrame 的前几行
正如你所看到的,行帮助我们看到我们的数据集是什么样子,但没有其他信息。因此,我们还可以使用以下代码查看我们的 DataFrame 的大小:
housing_data.shape
当我们运行上述代码时,我们会得到以下输出:
(21613, 21)
如你所见,现在我们有了 DataFrame 的形状或大小。*这是什么意思?*这意味着我们有21,613
行数据,21
列。无论你是在 Spyder、Python 控制台还是其他你选择的环境中,你都可以看到你的数据已成功导入。
现在我们已经导入了原始数据,让我们看看是否可以获得更多信息。我们可以使用以下代码来获取摘要:
housing_data.describe()
describe()
函数生成一个包括均值、标准差和百分位数等细节的摘要。这个百分位数是数据集五数概括的一部分,用于创建箱线图。虽然我们不会在这个问题中创建箱线图,但这种可视化表示对我们算法的目标可能有所帮助。看一下使用describe()
函数的结果的以下截图:
图 14.10 - 使用 describe()函数
现在,我们应该注意,该函数分析 DataFrame 中的数值。它排除了NaN
值。NaN
代表 Python 中的不是数字值,并表示任何未定义或不可表示的值。NaN
也可以表示缺失的数字。我们之前在处理缺失数据部分讨论过这些缺失的数字以及我们可以解决它们的一些方法。现在让我们在这个背景下看一下。我们想找到我们的缺失值。为此,我们可以运行以下代码片段:
housing_data.isnull().sum()
上面的片段将让我们知道我们的数据集中每个列中是否有缺失数据点。然后,它将聚合值作为总和。因此,如果在“日期”列下有两个缺失值,我们期望在那里看到2
。我们的结果可以在以下截图中看到:
图 14.11 - 运行 isnull()和.sum()函数的结果
我们不必清理这个数据集! 这是一个干净的数据集。然而,学习如何识别我们是否需要这样做非常重要。现在我们知道我们有一个干净的数据集,我们可以开始构建一些可视化。
使用数据可视化
请记住,这仍然是我们初始算法文件ch15_housePrice_prediction.py
的一部分。如果你打开该文件,接下来的代码从第 25 行开始。我们在代码行之间添加了描述性的注释:
names=['price','bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront','view','condition','grade','sqft_above','sqft_basement','zipcode','lat','long']
df=housing_data[names]
我们要做的第一件事是确定我们将用于图表的列。在这样做之后,我们将制作一个 DataFrame 并将其保存为df
:
correlations= df.corr()
前面的代码片段创建了我们 DataFrame 的相关性。在文件的下一个代码片段中,我们将创建我们的图表;也就是说,我们将进行数据可视化:
fig=plt.figure()
ax=fig.add_subplot(111)
cax=ax.matshow(correlations,vmin=-1,vmax=1)
fig.colorbar(cax)
在前面的代码中,我们命名了我们的图表,添加了子图,并确定了我们的颜色。接下来,我们需要设置一些属性,比如刻度标记、刻度标记之间的距离以及轴和刻度标记的标签:
ticks=np.arange(0,15,1)
ax.set_xticks(ticks)
ax.set_yticks(ticks)
ax.set_xticklabels(names, rotation =' 90')
ax.set_yticklabels(names)
在设置了一些属性之后,我们可以要求图表使用tight_layout()
。这将帮助我们看到图表和标签的所有细节。如果我们不使用tight_layout()
,有时在我们的图表中会有一些标签是不可见的:
plt.tight_layout()
plt.savefig('Correlation_graph.png',dpi = 300)
plt.show()
在前面的代码片段中,我们还创建了一个保存文件并定义了图表的大小。最后,我们要求算法显示我们的相关性。以下截图显示了前面代码的结果:
图 14.12 - 住房数据的相关性图
正如你所看到的,我们刚刚使用 Python 创建了一个相关性矩阵。而且这是一个相当不错的矩阵。但是什么是相关性矩阵呢? 相关性矩阵查看我们 DataFrame 中的所有值,然后计算它们之间的相关程度,给出一个在**-1和1之间的值。数值越接近1**,这些值之间的相关性就越高。每个值与自身进行比较,这是完美的相关性,当然,在我们的矩阵中被视为对角线。
其余的图表,所有的值都相互比较,必须更仔细地观察。越接近黄色,值之间的相关性就越高。因此,看一下y轴的sqft_above值和x轴上sqft_living的对应值。该值接近于黄色值1,但不完全相等。我们在以下截图中突出显示了这些值:
图 14.13 - 突出显示的 sqft_above 和 sqft_living 的相关性
还有其他一些值显示出一定的相关性,但不够强。这个图表帮助我们做出决策,也许要更仔细地观察这种相关性,找到更多关于它的信息等。
现在,我们还可以看一下一个相当密集的图表矩阵,称为对图。对图向我们展示了单个变量的分布以及两个变量之间的关系。如果我们对我们算法中包含的数据运行对图,我们会得到一个庞大的图表,如下截图所示(请注意,由于分析和绘制的数据量较大,这个图表可能需要几分钟的时间来生成):
图 14.14 - 完整的 DataFrame 对图
正如你所看到的,这是一个相当密集的、难以阅读的、几乎不可能分析的图表。你需要放大到每对图形来做出一些决定。还应该提到的是,算法需要时间来运行并生成这个图形。从如此庞大的数据集中创建如此复杂的东西需要一定的处理能力!
我们是否想要看到像这样的大量图表?实际上是的。如果我们有较少的变量数据集,那绝对可以!我们还可以使用其他颜色方案来使其更加友好,例如。这可能会使识别趋势变得更容易。但让我们明确一点;这并不是非常有帮助。我们可以创建一些感兴趣的变量的成对图。记得我们谈到过sqft_living
和sqft_above
可能有很强的正相关性吗?我们也确实想要将事物与定价进行比较,对吧?所以,让我们只使用sqft_living
、pricing
和sqft_above
创建一个成对图。看一下我们文件中相关代码片段:
coln = ['price','sqft_living','sqft_lot']
sns.pairplot(housing_data[coln], height = 3);
plt.savefig('pairplotting.png',dpi =300)
plt.show()
现在,当我们运行算法的这一部分时,我们得到了下面截图中显示的图表。这个图表提供了这三个值的相关性,我们确实可以看到一些正相关发生:
图 14.15 - 价格、sqft_living 和 sqft_above 的成对图
特别注意一下成对图中的sqft_living和sqft_above。这两者之间的关系相当线性和正向。这证实了我们从图 14.13中观察到的情况,其中相关性比其他变量更接近1。
但是,分析另外三个变量也会有所帮助,这样我们就可以看到当相关性不强时会发生什么。我们将保留price
和sqft_living
,这样我们只改变一个变量进行比较。看一下图 14.12,sqft_living和zipcode似乎根本没有强烈的正相关性。因此,让我们再次运行算法,将zipcode
替换为sqft_above
。让我们看一下下面截图中显示的结果:
图 14.16 - 价格、sqft_living 和 zipcode 的成对图
正如你所看到的,sqft_living和zipcode根本没有相关性。它们看起来更像条形图,看不到对角线。在我们离开这些图之前,值得一提的是,这些成对图仅为比较的变量提供散点图,并为图中的每个变量提供直方图。如果我们想深入了解,我们可以查看其他可视化工具,比如Seaborn中的工具。
我们在这里暂停分析。我们使用可视化来了解我们的数据有哪些相关性。如果我们进一步处理这个问题,我们可以使用数据来创建训练模型,并帮助我们进行预测。即使只是从图形数据中,我们也可以看到我们的相关性,并利用它进行预测。例如,如果我们有sqft_living
,我们可以预测sqft_above
,因为它们之间有很强的相关性。
Python 允许我们以多种方式查看数据。这是这样一个工具的伟大优势之一。
总结
在本章中,我们学习了如何使用 Python 处理数据,并使用本章学到的一些概念来处理房屋数据集。我们了解了 pandas 包以及它如何帮助我们组织和准备数据。我们还了解了预处理数据集的必要性,特别是在非常大的数据集中。我们处理了缺失和嘈杂的数据,以及数据转换和数据减少。我们还学会了如何使用可视化,为我们的数据集创建图表,可以帮助我们识别相关性和趋势。
本章的主题非常广泛,有整本书专门讨论这些主题。但在我们继续本书的下两章之前,我们觉得有必要分享一些 Python 编程语言的能力。
在接下来的章节中,我们将完全专注于应用程序,使用问题场景和主题来分享 Python 和计算思维在设计算法中的一些令人兴奋的应用。
第十五章:应用计算思维问题
在本章中,我们将提供多个领域的 Python 编程语言和计算思维应用示例。我们将探索多个领域,如人文学科、语言学、密码学等。我们将利用我们迄今为止学到的关于计算思维和Python编程语言的知识来做以下事情:
-
分析历史演讲
-
编写故事
-
计算文本可读性
-
找到最有效的路线
-
实现密码学
-
实现网络安全
-
创建聊天机器人
这一章与其他章节不同,因为我们将专注于提出问题并在评估每种情况后提供算法解决方案。
技术要求
您需要安装最新版本的 Python 才能运行本章中的代码。
您需要为 Python 安装以下库和软件包:
-
NLTK
-
Cairos
-
Pandas
-
Matplotlib
-
Seaborn
您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter15
问题 1 - 使用 Python 分析历史演讲
历史非常迷人,我们有很多原因要编写算法来评估历史数据和背景。
对于这个问题,我们想要分析一些历史文本。特别是,我们将研究亚伯拉罕·林肯的第二次就职演讲。我们的目标是找到一些单词的频率。有很多原因我们想要进行一些简单的文本分析,特别是对于历史文本。我们可能想要进行比较,了解潜在的主题等等。
对于我们的算法,我们将使用nltk
包的一个相当简单的设计。由于一些组件的安装与我们迄今为止所做的有些不同,我们将提供一些信息,以防您的软件包尚未安装。
在 Python shell 中,如果您在活动控制台中,创建一个新文件并在安装主要软件包后导入nltk
(使用pip install nltk
)。
请注意,您不应该在活动的Shell窗口中。如果您在行首看到>>>
,请单击文件 | 新建文件选项,然后输入以下代码指令行以创建空的 shell:
import nltk
nltk.download()
从前面的代码中可以看到,您还将在nltk
中打开下载器。前面的代码将弹出一个窗口,如下面的屏幕截图所示(请注意,nltk
库大约需要 7MB 的内存,而安装额外的软件包也需要内存,每个软件包的范围从几 KB 到 14 到 15MB 不等):
图 15.1 - NLTK 下载器
如您所见,我的软件包都已安装。如果您的软件包尚未安装,请选择全部,然后单击该窗口左下角的下载按钮。软件包安装完成后,您可以关闭该窗口。
因为我们的问题相当简单,所以我们将跳过这个特定部分的大部分计算思维过程。我们只是试图获得演讲中使用的单词的频率。所以让我们直接进入算法,看看我们将如何使用nltk
包来获得我们需要的内容,包括数据的可视化表示:
- 首先,我们需要导入
nltk
和word_tokenize
函数。word_tokenize
函数允许我们将演讲分成单个单词和/或标点符号。我们需要演讲文本。在这种情况下,演讲被复制到算法中。您也可以将文件导入算法并以这种方式进行操作。
sent_tokenize
函数是句子标记化的缩写。与单词标记化类似,句子标记化函数允许将文本分成完整的句子。输出将包含一个由逗号分隔的句子列表。
重要提示:
重要的是要知道,所有引号都已经使用 \'
或 \"
进行了转义,以保持原始文本而不在我们的代码中创建错误。
以下算法包含了我们需要的一切,以便分析亚伯拉罕·林肯的第二次就职演说:
ch15_historicalTextAnalysis.py
import nltk
from nltk.tokenize import sent_tokenize, word_tokenize
演讲的整个文本包含在 GitHub 存储库文件中。出于长度目的,我们在这里只包含了其中一半的文本。请注意,我们将在算法和解释之后分享的输出将对应于截断的演讲,但可视化图将包含整篇演讲的数据。在 text
定义的末尾看到的 […]
仅用于显示那里有额外的文本。
text = 'Fellow-Countrymen: At this second appearing to take the oath of the Presidential office there is less occasion for an extended address than there was at the first. Then a statement somewhat in detail of a course to be pursued seemed fitting and proper. Now, at the expiration of four years, during which public declarations have been constantly called forth on every point and phase of the great contest which still absorbs the attention and engrosses the energies of the nation, little that is new could be presented. The progress of our arms, upon which all else chiefly depends, is as well known to the public as to myself, and it is, I trust, reasonably satisfactory and encouraging to all. With high hope for the future, no prediction in regard to it is ventured. On the occasion corresponding to this four years ago all thoughts were anxiously directed to an impending civil war. All dreaded it, all sought to avert it. While the inaugural address was being delivered from this place, devoted altogether to saving the Union without war, urgent agents were in the city seeking to destroy it without war—seeking to dissolve the Union and divide effects by negotiation. Both parties deprecated war, but one of them would make war rather than let the nation survive, and the other would accept war rather than let it perish, and the war came. One-eighth of the whole population were colored slaves, not distributed generally over the Union, but localized in the southern part of it. These slaves constituted a peculiar and powerful interest. All knew that this interest was somehow the cause of the war. To strengthen, perpetuate, and extend this interest was the object for which the insurgents would rend the Union even by war, while the Government claimed no right to do more than to restrict the territorial enlargement of it. Neither party expected for the war the magnitude or the duration which it has already attained. […]'
- 现在我们已经定义了我们想要分析的文本,如前面的代码片段所示,我们可以告诉算法我们想要对文本进行标记化,也就是说,我们希望将其分成单词。算法将打印出一个包含每个单词或标点符号的列表,用逗号分隔,如下面的代码所示:
tokenized_word = word_tokenize(text)
print(tokenized_word)
- 在我们有了单词列表之后,我们想要得到单词的频率分布。为此,我们将从
nltk.probability
导入包,如下面的代码片段所示:
from nltk.probability import FreqDist
fdist = FreqDist(tokenized_word)
print(fdist)
fdist.most_common(2)
- 一旦我们有了分布,我们就希望得到这些数据的可视化图表,因此我们将使用
matplotlib
来创建我们的分布图,如下面的代码片段所示:
import matplotlib.pyplot as plt
fdist.plot(30, cumulative = False)
plt.show()
这就是我们需要的整个算法。当我们运行算法时,我们的输出如下:
['Fellow-Countrymen', ':', 'At', 'this', 'second', 'appearing', 'to', 'take', 'the', 'oath', 'of', 'the', 'Presidential', 'office', 'there', 'is', 'less', 'occasion', 'for', 'an', 'extended', 'address', 'than', 'there', 'was', 'at', 'the', 'first', '.', 'Then', 'a', 'statement', 'somewhat', 'in', 'detail', 'of', 'a', 'course', 'to', 'be', 'pursued', 'seemed', 'fitting', 'and', 'proper', '.', 'Now', ',', 'at', 'the', 'expiration', 'of', 'four', 'years', ',', 'during', 'which', 'public', 'declarations', 'have', 'been', 'constantly', 'called', 'forth', 'on', 'every', 'point', 'and', 'phase', 'of', 'the', 'great', 'contest', 'which', 'still', 'absorbs', 'the', 'attention', 'and', 'engrosses', 'the', 'energies', 'of', 'the', 'nation', ',', 'little', 'that', 'is', 'new', 'could', 'be', 'presented', '.', 'The', 'progress', 'of', 'our', 'arms', ',', 'upon', 'which', 'all', 'else', 'chiefly', 'depends', ',', 'is', 'as', 'well', 'known', 'to', 'the', 'public', 'as', 'to', 'myself', ',', 'and', 'it', 'is', ',', 'I', 'trust', ',', 'reasonably', 'satisfactory', 'and', 'encouraging', 'to', 'all', '.', 'With', 'high', 'hope', 'for', 'the', 'future', ',', 'no', 'prediction', 'in', 'regard', 'to', 'it', 'is', 'ventured', '.', 'On', 'the', 'occasion', 'corresponding', 'to', 'this', 'four', 'years', 'ago', 'all', 'thoughts', 'were', 'anxiously', 'directed', 'to', 'an', 'impending', 'civil', 'war', '.', 'All', 'dreaded', 'it', ',', 'all', 'sought', 'to', 'avert', 'it', '.', 'While', 'the', 'inaugural', 'address', 'was', 'being', 'delivered', 'from', 'this', 'place', ',', 'devoted', 'altogether', 'to', 'saving', 'the', 'Union', 'without', 'war', ',', 'urgent', 'agents', 'were', 'in', 'the', 'city', 'seeking', 'to', 'destroy', 'it', 'without', 'war—seeking', 'to', 'dissolve', 'the', 'Union', 'and', 'divide', 'effects', 'by', 'negotiation', '.', 'Both', 'parties', 'deprecated', 'war', ',', 'but', 'one', 'of', 'them', 'would', 'make', 'war', 'rather', 'than', 'let', 'the', 'nation', 'survive', ',', 'and', 'the', 'other', 'would', 'accept', 'war', 'rather', 'than', 'let', 'it', 'perish', ',', 'and', 'the', 'war', 'came', '.', 'One-eighth', 'of', 'the', 'whole', 'population', 'were', 'colored', 'slaves', ',', 'not', 'distributed', 'generally', 'over', 'the', 'Union', ',', 'but', 'localized', 'in', 'the', 'southern', 'part', 'of', 'it', '.', 'These', 'slaves', 'constituted', 'a', 'peculiar', 'and', 'powerful', 'interest', '.', 'All', 'knew', 'that', 'this', 'interest', 'was', 'somehow', 'the', 'cause', 'of', 'the', 'war', '.', 'To', 'strengthen', ',', 'perpetuate', ',', 'and', 'extend', 'this', 'interest', 'was', 'the', 'object', 'for', 'which', 'the', 'insurgents', 'would', 'rend', 'the', 'Union', 'even', 'by', 'war', ',', 'while', 'the', 'Government', 'claimed', 'no', 'right', 'to', 'do', 'more', 'than', 'to', 'restrict', 'the', 'territorial', 'enlargement', 'of', 'it', '.', 'Neither', 'party', 'expected', 'for', 'the', 'war', 'the', 'magnitude', 'or', 'the', 'duration', 'which', 'it', 'has', 'already', 'attained', '.']
- 请回想一下,
单词标记化
只包括了截断的文本。然而,随后的频率信息和图表是针对整篇演讲的。ch15_historicalTextAnalysis.py
GitHub 文件包含了完整的演讲:
<FreqDist with 365 samples and 782 outcomes>
以下屏幕截图显示了该算法的频率分布可视化图:
图 15.2 - 亚伯拉罕·林肯第二次就职演说的频率分布图
一旦我们有了这些信息,我们就可以开始更仔细地查看最常用的单词。在进行这种分析时,您可能希望考虑删除一些单词,比如to、a和the,因为它们对我们的分析没有意义。但是,像years和Union这样的单词可能与我们的分析相关。
这个算法可以进行许多调整,但目前,我们至少已经成功为一个历史演讲制作了一个频率分布图。现在,我们将继续下一个问题。
问题 2 - 使用 Python 编写故事
让我们看一个相当简单的问题。在这一部分,我们想要创建一个基于用户输入的算法来生成一个故事。我们可以尽可能简单,或者添加一些选项。但让我们深入了解一下这是什么。
定义、分解和规划故事
首先,我们要创造什么? 好吧,一个故事。由于这个问题的性质,我们将从反向开始,用我们想要实现的输出样本开始,也就是一个样本故事。在我们真正进入算法之前,让我们先看一下我们的算法生成的一个快速故事:
There once was a citizen in the town of Narnia, whose name was Malena. Malena loved to hang with their trusty dog, King Kong.
You could always see them strolling through the market in the morning, wearing their favorite blue attire.
前面的输出是由一个算法创建的,该算法替换了名字、地点、时间、宠物和宠物名字。这是一个简短的故事,但这是可以在更广泛的应用中使用的东西,比如使用输入来撰写社交媒体帖子,并为邀请、表格等填写信息。
所以让我们倒退一点来编写我们的算法。*为什么这次我从结尾开始呢?*嗯,在这种情况下,我们知道我们想要的结果。您可以编写您的故事。您可以有一个需要填写的婚礼邀请模板的示例,或者表格。现在我们必须弄清楚如何获取输入,然后输出我们想要的内容。
从所示的故事中,这是我们可以获得原始输入的事情:
-
角色的名字
-
城镇名称
-
宠物的类型
-
宠物的名字
-
参观的城镇的一部分
-
一天中的时间
-
喜欢的颜色
当我们编写我们的算法时,我们需要获取所有上述输入。让我们来看看ch15_storyTime.py
文件中的算法:
- 我们需要用户的输入,所以我们想使用一个
print
语句和包含所需指令的输入请求:
print('Help me write a story by answering some questions. ')
name = input('What name would you like to be known by? ')
location = input('What is your favorite city, real or imaginary? ')
time = input('Is this happening in the morning or afternoon? ')
color = input('What is your favorite color? ')
town_spot = input('Are you going to the market, the library, or the park? ')
pet = input('What kind of pet would you like as your companion? ')
pet_name = input('What is your pet\'s name? ')
前面的代码片段从用户那里获取所有输入,这样我们就可以写我们的故事了。
- 一旦我们有了这些,我们就必须
print
我们的故事。请注意,我们用简单的术语写了它,使用%s
,这样我们就可以用相应的输入替换它。我们还使用反斜杠,这样我们就可以在多行上看到我们的代码,而不是将其放在一行上:
print('There once was a citizen in the town of %s, whose name was %s. %s loved to hang \
with their trusty %s, %s.' % (location, name, name, pet, pet_name))
print('You could always see them strolling through the %s \
in the %s, wearing their favorite %s attire.' % (town_spot, time, color))
让我们再次运行那段代码,看看我们的故事现在说了什么:
Help me write a story by answering some questions.
What name would you like to be known by? Azabache
What is your favorite city, real or imaginary? Rincon
Is this happening in the morning or afternoon? afternoon
What is your favorite color? magenta
Are you going to the market, the library, or the park? library
What kind of pet would you like as your companion? dog
What is your pet's name? Luna
There once was a citizen in the town of Rincon, whose name was Azabache. Azabache loved to hang with their trusty dog, Luna.
You could always see them strolling through the library in the afternoon, wearing their favorite magenta attire.
请注意,角色和设置等细节已经发生了变化。在教育学习环境中,这样一个简单的算法可以成为一个很好的工具,用来向学生展示如何与故事互动并识别其中的关键信息。
虽然这是一个三句故事,但这些算法可以更加复杂,为用户输入提供了编写出色原创故事的机会。如果您想尝试一些内容,甚至可以根据一些输入条件更改使用的短语,例如根据输入的名称长度更改使用的句子。用代码和写一些故事来玩一玩吧!
问题 3-使用 Python 计算文本可读性
在这一部分,我们将看一个与语言学相关的应用,具体来说是任何文本的可读性水平。我们将在接下来的代码片段中使用马丁·路德·金的我有一个梦演讲。您可以用任何文本文件替换它,只要您在代码中准确反映文件和文件名的位置。完整的代码可以在ch15_Readability.py
文件中找到。
在我们进入代码之前,让我们先谈谈我们要寻找的内容以及为什么它很重要。了解文本的可读性可以帮助我们决定是否将其包含在演示文稿中,学校年级水平等等。弗莱施-金凯德分数用于确定可读性,并于 1940 年代开发。
Rudolf Flesch 在与美联社合作期间创建了它,以改善报纸的可读性。最初被称为弗莱施阅读易度,它被现代化为目前美国海军使用的形式。现在,弗莱施-金凯德分数提供了一个年级水平分数,而不是得到一个分数,然后必须将其转换为年级水平。
虽然我们不会使用这个公式,但了解我们使用的背景很重要。弗莱施阅读易度公式如下:
弗莱施-金凯德年级公式如下:
前面的公式的事实是,它们存在于 Python 可用的可读性包中。如果我们导入这个包,我们就能用一小段相当简单的代码进行可读性分析。
让我们来看看我们需要执行马丁·路德·金演讲的可读性分析所需的代码:
- 首先,记得在代码中更改文件的路径,然后导入代码所需的必要包:
ch15_readability.py
from readability import Readability
text = open('C:\\...\\ch15_MLK-IHaveADream.txt')
text_up = text.read()
r = Readability(text_up)
flesch_kincaidR = r.flesch_kincaid()
从前面的代码中,您会看到我们将readability
包导入到我们的程序中。如果您需要安装库/包,可以使用pip install readability
来安装。
一旦我们有了必要的库,我们就可以打开要分析的文件。我们还希望告诉算法读取文本,我在这里称之为text_up
,以便上传文本,这样我就不会忘记我正在读取一个打开的文件。这是我们从前面的代码中打开的文本。最后,我们要求程序使用Readability
函数分析文本。请注意,我们将其保存到r
中。
- 在我们完成所有这些之后,我们可以使用以下代码片段
print
我们的年级水平:
print('The text has a grade '+ flesch_kincaidR.grade_level + ' readability level.')
当我们运行我们的算法时,输出也是非常简单的。看一下以下输出:
The text has a grade 9 readability level.
现在您知道如何验证任何文本的可读性,尝试对其他类型的文本执行分析,包括诗歌、故事、演讲、歌曲等。
问题 4 - 使用 Python 找到最有效的路线
对于这个问题,在学习算法时,我们将使用一个常见的算法——旅行推销员问题(TSP)。让我们来设置问题本身。
一个推销员需要前往一定数量的城市或地点。假设推销员有 10 个地点要去。他们可以以许多不同的顺序去这 10 个地点。我们这个算法的目标是创建最佳的、最有效的路线,以到达这些地点。
请注意,对于这种特定情况,就像我们将在下一个问题中所做的那样,我们将使用计算思维过程的四个元素进行简单的分析。
定义问题(TSP)
这个问题比起最初的样子要复杂一些。可以这样想。如果我们有 10 个目的地,我们正在计算往返排列以检查最快的路线,我们将得到超过 300,000 个可能的排列和组合。提醒一下,排列考虑顺序,而组合则不考虑。
例如,数字3344和3434是两种不同的排列。但是,它们只被计为一种组合,因为数字的顺序并不重要。
但回到我们的问题。我们只需要知道我们想要创建一个算法,以最有效的方式带我们到达目的地。我们必须确定要访问的城市,并确定我们将如何旅行。
-
总共有五个城市,分别是纽约(NYC)、费城、巴尔的摩、芝加哥和克利夫兰。
-
我们将使用一辆车,因为我们使用的是 TSP 而不是车辆路径问题(VRP)。
-
第一个城市是 0,也就是纽约。纽约和自身之间的距离是 0。
现在让我们来看一下模式。
识别模式(TSP)
对于每个城市,将有五个距离,到自身的距离等于 0。我们将需要一个数组或列表,用于存储每个城市的所有距离。我们需要创建一个模型,以便在我们的算法中访问数据。我们将在设计算法时进行讨论。首先,让我们稍微概括一下模式。
概括(TSP)
对于这个特定的问题,我们将手动将城市输入到算法中。您可能需要考虑的一件事是如何从用户那里获取输入,以便创建必要的包含距离的数组。
您还可以创建一个数据库,记录主要城市之间的距离,可以从.csv
文件中访问,这样输入的城市数据可以在其中找到,然后添加到我们的模型中。对于这个特定算法,有许多补充,这不是一个只有一种解决方法的问题。现在,我们将使用一组定义好的城市,以便创建我们的算法。
顺便说一句,我们已经参考了来自developers.google.com/optimization/routing/tsp
的源代码。
设计算法(TSP)
是时候看看我们一直在谈论的东西了。让我们从纽约开始,首先构建那个数组。其他数组以相同的方式创建。所有距离都是以英里为单位,并根据Google Maps的数据进行了近似和四舍五入,如下所示:
-
从纽约到纽约的距离是 0。
-
从纽约到费城的距离是 95。
-
从纽约到巴尔的摩的距离是 192。
-
从纽约到芝加哥的距离是 789。
-
从纽约到克利夫兰的距离是 462。
以下表格显示了每个城市到其他城市及其自身的距离:
表 15.1 - 从一个城市到另一个城市的距离
因此,如您在上表中所见,如果我们将这些距离写成一个数组,我们将使用以下代码:
[0, 95, 192, 789, 462]
对于费城,我们将有以下数组:
[95, 0, 105, 759, 431]
对于巴尔的摩,我们将有以下数组:
[192, 105, 0, 701, 374]
对于芝加哥,我们将有以下数组:
[789, 759, 701, 0, 344]
最后,对于克利夫兰,我们将有以下数组:
[462, 431, 374, 344, 0]
请注意,我们将为每个城市分配索引以便识别它们。纽约是0,费城是1,巴尔的摩是2,芝加哥是3,克利夫兰是4。让我们看看这个问题的算法是什么样子的(请注意,OR-Tools 库用于优化车辆路线、线性规划、约束规划等):
- 首先,让我们开始导入我们需要的包和库。这个算法的完整文件是
ch15_travel.py
,可以在 GitHub 上找到:
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
请记住,如果您计划访问更多的城市和/或不同的城市,这个算法需要获取一个新的距离矩阵。这是您需要更改的代码的唯一部分。您每次需要调整的代码片段是create_data_model()
下的矩阵,如下面的代码片段所示:
#Create data model.
def create_data_model():
data = {}
data['distance_matrix'] = [
[0, 95, 192, 789, 462],
[95, 0, 105, 759, 431],
[192, 105, 0, 701, 374],
[789, 759, 701, 0, 344],
[462, 431, 374, 344, 0],
]
data['num_vehicles'] = 1
data['depot'] = 0
return data
- 在我们定义了数据模型之后,我们需要打印一个解决方案。以下函数提供了该信息:
#Provide solution as output - print to console
def print_solution(manager, routing, solution):
print('Objective: {} miles'.format(solution.ObjectiveValue()))
index = routing.Start(0)
plan_output = 'Route for vehicle 0:\n'
route_distance = 0
while not routing.IsEnd(index):
plan_output += ' {} ->'.format(manager.IndexToNode(index))
previous_index = index
index = solution.Value(routing.NextVar(index))
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
plan_output += ' {}\n'.format(manager.IndexToNode(index))
print(plan_output)
plan_output += 'Route distance: {}miles\n'.format(route_distance)
如您从上述代码中所见,我们正在创建一个函数,以便我们可以根据我们的数组和这些数组中的距离打印解决方案。请记住,您将确定出发点,也就是您要离开的城市。然后我们运行算法来收集信息并创建我们的print
语句。
- 最后,我们需要定义我们的
main()
函数以运行我们的算法。main()
函数告诉算法继续创建我们已经定义的数据模型,并将其存储为数据。然后我们创建路由模型来找到我们的解决方案。请看以下代码片段:
def main():
data = create_data_model()
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
data['num_vehicles'], data['depot'])
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
def distance_callback(from_index, to_index):
"""Returns the distance between the two nodes."""
# Convert from routing variable Index to distance matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return data['distance_matrix'][from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
solution = routing.SolveWithParameters(search_parameters)
if solution:
print_solution(manager, routing, solution)
if __name__ == '__main__':
main()
上述代码显示了我们如何定义我们的main()
函数。需要注意的是,main()
函数可以被命名为任何我们想要的名称。在使用多个函数时,我们有时使用main()
来标识最初从算法中输出我们想要的功能。对于这个问题,我们正在创建一个main()
函数,它将确定我们旅行的最佳路线。
- 现在让我们看看当我们运行这段代码时,我们得到了什么输出。该代码为我们提供了里程的“目标”总数以及我们应该采取的行程路线。以下是输出:
Objective: 1707 miles
Route for vehicle 0:
0 -> 1 -> 2 -> 4 -> 3 -> 0
从纽约到纽约的行程,如果我们按照以下顺序进行,将会是最有效的:纽约 | 费城 | 巴尔的摩 | 克利夫兰 | 芝加哥 | 纽约。
这不是旅行问题的唯一方法。如果我们想要一天运行多次这个问题,或者为不同的旅行者运行,这也不一定是最用户友好的方法。为了做到这一点,您需要自动化更多的内容,就像前面的例子中提到的那样。您可能要考虑的一些事情如下:
-
能够输入城市
-
拥有一个抓取信息以确定距离的计算器
-
使用自动化过程创建距离矩阵
但是现在,你已经看到 TSP 在行动中了!我们将在下一节中看一个新问题。
问题 5 – 使用 Python 进行密码学
密码学是我们用来编码和解码消息的工具。在第九章中,我们使用了一个简单的凯撒密码,理解输入和输出以设计解决方案算法。对于这个问题,我们将使用 Python 中可用的一些包来加密和解码信息。
请注意,对于这种特定情况,我们将使用计算思维过程的四个元素进行直接分析。虽然我们并不总是完全遵循它们,但这个特定问题本身就很适合进行相当直接的使用。
定义问题(密码学)
您正在从事一个机密项目,需要加密您的信息以保证其安全性。
识别模式(密码学)
Python 有一个可以安装的密码学包,就像我们安装其他库(如Pandas和NumPy)一样。在我们的问题中,我们需要知道的主要事情之一是,我们可能需要继续加密消息。我们可能还想解码我们收到的消息,但我们首先要专注于加密方面。
泛化(密码学)
当我们设计我们的算法时,我们希望能够在项目的整个生命周期中持续使用某些东西,而不需要太多的努力。也就是说,每当我们想要加密新消息时,我们可以运行算法并输入消息,而不是每次都将消息本身添加到算法体中。这是我们特定问题的通用模式。这意味着我们已经准备好设计了。
设计算法(密码学)
在编写我们的算法之前,让我们首先看一下我们需要做的事情:
-
定义字母。
-
将所有字母改为小写以运行我们的算法。
-
定义所需的函数——
加密
、解码
和main
。 -
调用密码学的
main
函数。
提示:
这个问题的完整算法可以在ch15_cryptographyA.py
文件中找到。
我们将按照以下步骤开始设计我们的算法:
- 让我们首先定义我们的字母。下面的代码片段定义了我们的字母,然后将每个字母转换为小写:
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ'
LETTERS = LETTERS.lower()
- 接下来,我们定义我们的加密函数。这个函数将接受两个参数——
message
和key
。message
函数将由用户定义,在main
函数中完成。现在,我们将通过在encryptedM
变量的定义中添加空引号(''
)来使用空消息,如下面的代码片段所示:
def encrypt(message, key):
encryptedM = ''
for letts in message:
if letts in LETTERS:
num = LETTERS.find(letts)
num += key
encryptedM += LETTERS[num]
return encryptedM
请注意,我们迭代要加密的消息中的字母,然后我们使用用户在main
函数中定义的密钥来加密消息。然后这个函数返回加密的消息。
但是为什么我们还没有定义 main
*函数,如果那是我们获取输入的地方?*因为main
函数将需要另外两个函数来加密或解码消息。所以请耐心等待;我们很快就会到达main
函数。
- 现在让我们来看一下解码函数。当我们有一个加密的消息并想知道原始消息是什么时,我们将使用这个函数:
def decode(message, key):
decodedM = ''
for chars in message:
if chars in LETTERS:
num = LETTERS.find(chars)
num -= key
decodedM += LETTERS[num]
return decodedM
前面的代码显示了我们将用来解码消息的函数。它使用消息中的字符和加密密钥来解码消息。请注意,如果没有原始密钥,您就无法解码消息,除非您有时间坐下来尝试每一个密钥。
- 最后,我们需要那个我们一直在提到的
main
函数。这个函数需要所有必要的输入,以便这个算法能够正确运行。以下是它正确运行所需的三件事情——要加密或解码的消息;密钥,可以是 1 到 26 范围内的任何数字;以及我们是在加密还是解码。
这是main
函数:
def main():
message = input('What message do you need to encrypt or decrypt? ')
key = int(input('Enter the key, numbered 1-26: '))
choice = input('Do you want to encrypt or decode? Type E for encrypt or D for decode: ')
if choice.lower().startswith('e'):
print(encrypt(message, key))
else:
print(decode(message, key))
if __name__ == '__main__':
main()
从前面的代码中可以看到,我们定义了一个main
函数。在代码的结尾,我们调用了该函数。不要忘记在算法中调用main
*函数!*这是让算法运行的方法。
当我们尝试使用密钥9
加密输入消息the name of the dog is King Kong
时,这是一个样本输出:
What message do you need to encrypt or decrypt? the name of the dog is King Kong
Enter the key, numbered 1-26: 9
Do you want to encrypt or decode? Type E for encrypt or D for decode: E
cqnwjvnxocqnmxprbrwpxwp
正如您所看到的,我们获得了加密文本cqnwjvnxocqnmxprbrwpxwp
作为密文,现在我们已经创建了一个可以加密或解密任何消息的算法。现在让我们继续解决一个新问题。
问题 6–在网络安全中使用 Python
对于这个问题,我们决定进行一个相当简短的网络安全检查。首先,让我们谈谈网络安全。根据Grand View Research的报告,预计到 2027 年,网络安全市场将增长 10%。
将这些内容转化为就业市场有点棘手。例如,在美国,网络安全的需求比市场上的人员或工作岗位更多。预计到 2028 年,该岗位市场增长率将略高于 30%。因此,学习一些关于网络安全和密码学的知识是有益的。
对于这个特定的问题,我们将探讨一些事情。首先,让我们谈谈哈希。在网络安全中,哈希意味着那些用数字和字母组成的非常长的字符串,用来替代密码之类的东西。例如,如果您输入了密码password1
(请不要这样做,永远不要使用password
作为密码),哈希过程会将其替换为看起来更像这样的东西:
27438d623d9e09d7b0f8083b9178b5bb8ff8bc321fee518af 4466f6aadb68a8f:100133bfdbff492cbc8f5d17af46adab
当我们创建密码算法时,我们必须添加随机数据,我们称之为盐。盐只是提供额外的输入,并在存储密码时帮助我们使密码更安全。
在 Python 中使用哈希时,我们可以使用uuid
库。UUID代表通用唯一标识符。当我们想要生成随机的 128 位对象作为 ID 时,就会使用uuid
库。但我们到底在谈论什么? 让我们来看看ch15_hashing.py
文件中的算法:
- 首先我们要导入库:
import uuid
import hashlib
我们正在导入两个库,这将允许我们使用加盐和哈希保存我们的密码。
- 在文件的下一个代码片段中,我们定义了对密码进行哈希的函数:
def hash_pwd(password):
salt = uuid.uuid4().hex
return hashlib.sha1(salt.encode() + password.encode()).hexdigest() + ':' + salt
我们使用我们的uuid
包对密码进行了加盐处理,然后使用安全哈希算法 1 sha1
返回了哈希值。这只是我们可以使用的算法之一。我们还可以使用其他算法,比如SHA-256,SHA-384等。sha1
哈希的输出大小为 160,而sha256
的输出大小为 256。sha1
和sha256
的块大小都为 512 位,而sha384
的块大小为 1,024 位。
在选择我们将使用的哈希以及它们的安全性等方面,所有这些都变得相关。我们在这里使用sha1
更多是出于怀旧,但它不像sha256
和sha384
那样安全。在受到攻击时,sha1
将无法抵御长时间的攻击。另外两者能够更长时间地抵挡,但仍然不是最好的选择。像shake128和shake256这样的哈希对抗这种攻击更成功。
- 现在让我们看一下
check
函数。我们总是希望通过要求两次输入密码来确认密码。以下代码片段定义了当接收到第二个密码时算法将执行的操作:
def check_pwd(hashed_pwd, user_pwd):
password, salt = hashed_pwd.split(':')
return password == hashlib.sha1(salt.encode() + user_pwd.encode()).hexdigest()
- 现在让我们要求一些输入。首先,我们会要求输入密码。因为我们对程序的操作很感兴趣,所以我们会打印出哈希密码,但在构建成网站或其他应用程序时,您可以省略该行。之后,我们要求验证密码,并为用户提供输出,以便他们知道它们是否匹配,如果匹配,我们可能会希望他们再试一次。目前,该算法要么确认它,要么让用户知道它现在已经确认:
new_pwd = input('Enter new password: ')
hashed_pwd = hash_pwd(new_pwd)
print('Hashed password: ' + hashed_pwd)
confirm_pwd = input('Confirm password: ')
if check_pwd(hashed_pwd, confirm_pwd):
print('Confirmed!')
else:
print('Please try again')
运行程序后,我们得到以下输出:
图 15.3–盐化和哈希密码确认的输出
如你从上述截图中所见,密码被系统确认了。
- 现在让我们看看当我们输入两个不同的密码时会发生什么。让我们看一下以下的截图:
图 15.4–带有确认失败的盐化和哈希密码的输出
如你所见,程序要求用户再试一次。然而,除非重新启动过程,算法并没有提供这样的方式。我们可以让它保持这样,或者我们可以添加条件,使程序再次运行一次,或者两次,或者无限次,直到确认达成为止。
- 现在让我们看看如果我们使用
sha256
而不是sha1
来运行算法会发生什么。以下截图显示了使用sha256
确认密码时的结果:
图 15.5–在确认密码的算法中用 sha256 替换 sha1 时的输出
请注意哈希值在sha256
算法方面的长度更长。在使用密码学时,随机和长总是有帮助的。破解一个不随机的密码,比如password
或mycat
,要比破解一个非常长并包含随机数字和字母的密码更容易。这就是为什么我们试图以能够保护数据免受攻击的方式存储数据。
- 让我们看看我们可以做些什么来为某人提供再次输入密码的机会。在算法结束时,让我们在最后一行之后添加一些代码:
new_pwd = input('Enter new password: ')
hashed_pwd = hash_pwd(new_pwd)
print('Hashed password: ' + hashed_pwd)
confirm_pwd = input('Confirm password: ')
if check_pwd(hashed_pwd, confirm_pwd):
print('Confirmed!')
else:
print('Please try again later')
请注意我们之前片段中的最后一句话是“请稍后重试”。这让用户知道,如果他们想要保存密码,他们将不得不重新开始这个过程。算法在那时停止了。
- 如果我们将上述代码放在我们的
else
、print()
语句之后,那么算法将再运行一次。以下截图显示了用户再次尝试时的输出:
图 15.6–在首次运行算法时输出不匹配后
在我们从这个例子中继续之前,请注意,尽管我们两次输入的新密码都是test
,但提供的哈希密码是不同的。正如我们提到的,哈希密码是每次创建的。否则,每个人都会知道密码是如何存储的,因为只要我们使用相同的哈希,比如sha256
,test
就会是相同的。
在网络安全和密码学中还有很多可以探索的内容。这只是我们如何加密信息的一个味道。
问题 7–使用 Python 创建一个聊天机器人
是时候创建一个简单的聊天机器人了。在过去几年里,你可能至少与十几个这样的聊天机器人互动过。当你访问一些网站时,你可能会遇到一个“人”想和你聊天,并问你一些简单的问题,比如你最近怎么样,以及他们能帮你什么。对于大多数网站来说,“人”并不是真人,而是一个聊天机器人。
在某些情况下,聊天机器人会将你引导到一个真正的人。但大多数时候,它们只会通过指向他们网站上可用答案的方向来回答你的问题。
我们将在这里创建类似那些聊天机器人的东西。在我们开始之前,有一些组件是我们需要的。其中之一是一个intents
文件。这个文件应该是一个.json
文件,包含了机器人将使用和/或回应的问候和回应。以下是intents
内容的样本:
{"intents": [
{"tag": "greeting",
"patterns": ["Hi", "How are you", "Hello?", "Welcome!", "Hello"],
"responses": ["Hello! Thank you for visiting our site! ", "Welcome back!", "Hello, how can I help you?", "What can I do for you? "],
"context_set": ""
},
{"tag": "goodbye",
"patterns": ["Bye", "See you later", "Goodbye"],
"responses": ["See you later, thanks for visiting", "Thank you and have a wonderful day!", "Bye! See you soon!"]
}
]
}
正如您所看到的,这只是一组可能的响应。我们给.json
文件提供的数据越多,我们的机器人就会越强大和准确。
我们应该注意,intents.json
文件需要在JSON 编辑器中进行编辑。您可以在jsoneditoronline.org
上使用一个在线编辑器来创建自己的文件,或者编辑现有的文件。
我们为什么需要一个机器人? 这样的东西有各种各样的用途,从创建和发布社交媒体上的消息到为客户提供一个询问他们是否需要帮助的机器人,例如当他们访问网页。这些只是我们可以用聊天机器人做的一些事情。
现在让我们来看一个创建聊天机器人的算法。完整的文件可以在存储库中找到。我们已经在一些片段中进行了注释和描述了发生的事情:
- 让我们从这里导入库开始:
ch15_chatBot.py
import nltk
import json
import pickle
import numpy as np
nltk.download('punkt')
nltk.download('wordnet')
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
从前面的代码中可以看出,您不必每次下载nltk
模块。但是拥有这段代码也不会有害。系统不会每次安装多个副本;它只会识别它们已经存在,并且不会第二次安装它们。
- 让我们继续从我们的库和软件包中获取我们需要的东西:
from keras.models import Sequential
from keras.optimizers import SGD
from keras.layers import Activation, Dense, Dropout
import random
- 现在我们已经得到了我们需要的东西,我们必须查看我们的
.json
文件。该文件包含了前面提到的意图。我们不仅需要打开该文件,还需要将组件分开并以我们的算法能够理解的方式进行排序。看一下以下代码片段:
#Upload intents file and create our lists
words=[]
classes = []
doc = []
ignore_words = ['?', '!', ',', '.']
data_words = open(r'C:\...\intents.json').read()
intents = json.loads(data_words)
请记住,除非您指定要访问的文件的正确位置,否则程序将无法运行,在这种情况下是.json
文件。还要注意,这次我们以稍微不同的方式打开它,如前面的代码片段所示。以这种方式打开文件,与我们用 Pandas 打开.csv
文件不同,意味着我们不需要在路径中使用双\\
。
- 现在让我们告诉算法如何处理该文件:
for intent in intents['intents']:
for pattern in intent['patterns']:
#Tokenize all the words (separate them)
w = nltk.word_tokenize(pattern)
words.extend(w)
#Add all the words into doc
doc.append((w, intent['tag']))
#Add the classes
if intent['tag'] not in classes:
classes.append(intent['tag'])
print(doc)
在这里,我们正在对我们的信息进行标记化,也就是说,我们正在将所有内容分解成单词,然后将它们添加到列表中。这就是使处理信息成为可能的东西。在我们将它们分开后,我们将根据单词的含义对它们进行分组。
- 然后这些单词被排序,如下面的代码片段所示:
#lemmatization
words = [lemmatizer.lemmatize(w.lower()) for w in words if w not in ignore_words]
words = sorted(list(set(words)))
classes = sorted(list(set(classes)))
pickle.dump(words,open('words.pkl','wb'))
pickle.dump(classes,open('classes.pkl','wb'))
请注意,在前面的代码中,我们使用了pickle()
。Pickle 是 Python 中的一个方法,我们可以使用它来序列化数据(或反序列化)。然后使用该方法替换当前文件数据,以便可以将其用作转换。
- 现在我们已经完成了所有这些,我们需要创建我们的训练模型。我们不会在这里逐个讨论该过程的所有部分,但整个代码可以在 GitHub 存储库文件中找到。请记住,您首先要训练,然后创建,然后编译模型。
一旦我们完成了这个过程,我们就会保存模型以便我们可以使用它。但现在让我们看看聊天机器人的功能:
#Define chatbot functions
def clean_up_sentence(sentence):
sentence_words = nltk.word_tokenize(sentence)
sentence_words = [lemmatizer.lemmatize(word.lower()) for word in sentence_words]
return sentence_words
def bow(sentence, words, show_details=True):
sentence_words = clean_up_sentence(sentence)
bag = [0]*len(words)
for s in sentence_words:
for i,w in enumerate(words):
if w == s:
bag[i] = 1
return(np.array(bag))
def predict_class(sentence, model):
p = bow(sentence, words,show_details=False)
res = model.predict(np.array([p]))[0]
ERROR_THRESHOLD = 0.25
results = [[i,r] for i,r in enumerate(res) if r>ERROR_THRESHOLD]
results.sort(key=lambda x: x[1], reverse=True)
return_list = []
for r in results:
return_list.append({"intent": classes[r[0]], "probability": str(r[1])})
return return_list
前三个函数用于为聊天机器人创建响应并进行预测。这些类将影响我们如何从我们的机器人那里获得这些响应。让我们这样考虑——如果我说你好,我不希望聊天机器人说再见。那将是不礼貌的。但请记住,我们的机器人只有我们的训练好,它才会有多好。因此,如果我们在.json
文件中没有足够的内容,并且没有正确训练模型,那么机器人将是相当无用的。
- 现在让我们定义如何获取响应:
def getResponse(ints, intents_json):
tag = ints[0]['intent']
list_of_intents = intents_json['intents']
for i in list_of_intents:
if(i['tag']== tag):
result = random.choice(i['responses'])
break
return result
def chatbot_response(msg):
ints = predict_class(msg, model)
res = getResponse(ints, intents)
return res
从前面的代码片段中可以看到,机器人将制作一个响应并返回它。我们将在我们的文件中的下几个代码片段中调用这些内容。
- 但我们将在这里跳过这一点,进入我们聊天机器人的外观:
base = Tk()
base.title("Chat with Customer Service")
base.geometry("400x500")
base.resizable(width=FALSE, height=FALSE)
请注意,我们在前面的代码片段中建立了一些关键信息。我们确定了窗口的大小并阻止了其调整大小。
- 在下一个代码片段中,我们将建立背景,添加
滚动条
,并确定发送按钮的外观:
#Create chatbot window
ChatLog = Text(base, bd=6, bg="white", height="8", width="70", font="Calibri")
ChatLog.config(state=DISABLED)
#Scrollbar
scrollbar = Scrollbar(base, command=ChatLog.yview, cursor="arrow")
#Create Send button
SendButton = Button(base, font=("Calibri",12,'bold'), text="Send", width="15", height=5,
bd=0, bg="pink", activebackground="light green",fg='black',
command= send )
EntryBox = Text(base, bd=0, bg="white",width="29", height="5", font="Arial")
scrollbar.place(x=376,y=6, height=386)
ChatLog.place(x=6,y=6, height=386, width=370)
EntryBox.place(x=128, y=401, height=90, width=265)
SendButton.place(x=6, y=401, height=90)
base.mainloop()
所以,这就是全部。我们创建了一个聊天机器人! 但是当它运行时会是什么样子呢? 看一下输出:
图 15.7 - 聊天机器人窗口
注意我们的组件,左侧的滚动条,粉色的发送按钮,以及我们聊天机器人的标题。还要注意最大化按钮是灰色的。这是因为我们说过我们不希望窗口被调整大小。
- 此外,当我们点击发送按钮时,我们希望用户知道它是否被点击。否则,如果不确定,您可能会多次点击它。这就是为什么代码中活动的背景颜色会改变。下面的截图显示了按钮在活动状态下的外观:
图 15.8 - 活动的发送按钮
许多聊天机器人都有类似的功能,以避免代码错误。
一旦我们打招呼,机器人就会回应。在我们离开这个问题之前,让我们看一下下面截图中与聊天机器人的快速对话:
图 15.9 - 带有响应的聊天机器人窗口
正如您所看到的,几行代码和一个文件可以用来创建与聊天机器人的交互体验。
随意玩弄代码,添加一些特色,创建一个不同的intents.json
文件,并使其更符合您的需求。
总结
在本章中,我们有机会在查看实际问题的同时,探索 Python 在一些非常不同的应用中的应用。
在之前的章节中,我们学习了计算思维过程,以及分解、模式识别、模式概括和算法设计的要素,这些使算法有意义。当我们解决来自客户的问题或者只是在业余时间创建脚本时,我们必须经历必要的过程来定义我们用算法创建的东西。这个关键过程将确保我们设计出尽可能好的算法。
在本章中,我们学会了如何从文件中读取、上传文件、创建密码和解码器、使用算法根据用户输入编写故事,并在给定我们将访问的城市时制定最有效的旅行计划。此外,我们还创建了一个基本的聊天机器人,它可以根据用户输入进行交互和适应。
在下一章中,我们将继续探索 Python 和计算思维,在科学应用、房地产、股票市场分析等领域中解决数据分析的附加应用问题。
第十六章:高级应用计算思维问题
在本章中,我们将继续提供 Python 编程语言和计算思维在多个领域的应用示例。我们将探索多个领域,如几何镶嵌、创建房屋数据模型、创建电场、分析基因数据、分析股票、创建卷积神经网络(CNN)等。我们将利用我们迄今为止学到的关于计算思维和 Python 编程语言的知识来做以下事情:
-
创建镶嵌
-
分析生物数据
-
分析特定人群的数据
-
创建房屋数据模型
-
创建电场线
-
分析通用数据
-
分析股票
-
创建卷积神经网络(CNN)
阅读本章后,您将学会如何在处理数据、创建表格和图形以帮助分析现有数据的同时,创建训练和测试模型以帮助根据现有大型数据集预测结果。
技术要求
您需要安装最新版本的 Python 来运行本章中的代码。
您需要为 Python 安装以下库和包:
-
NLTK
-
Cairos
-
Pandas
-
Matplotlib
-
Seaborn
您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter16
问题 1 - 使用 Python 创建镶嵌
在本节中,我们将使用 Python 的cairo
库提供一个示例。我们将创建一个镶嵌,更具体地说,是一个彭罗斯镶嵌的样本。因为这是一个简单的问题,我们将使用计算思维过程来定义我们的参数,但不会严格遵循它。
首先,让我们谈谈cairo
库。一旦pip install cairo
命令成功,您需要执行一个步骤来包含所有需要的组件。使用pip install pycairo
来添加必要的组件。cairo
和pycairo
包是与 Python 一起使用的图形库。有关更多信息,您可以访问它们的网页:cairographics.org/pycairo
。
现在让我们定义一些东西。镶嵌是使用不重叠的形状进行平铺以创建图案。镶嵌经常在几何课程中探索。在我们的示例中,我们将使用两个三角形创建一个彭罗斯镶嵌图案。我们还将定义我们的空间和我们希望形状经历的子细分数。子细分数越多,空间中的图案就越小。让我们看一下算法(文件ch16_tessellation.py
包含了这里讨论的完整算法):
- 我们要做的第一件事是导入必要的包和库:
ch16_tessellation.py
import math
import cmath
import cairo
- 接下来,我们要定义我们的画布和子细分数。请注意,我们选择了
4
作为我们的示例。在图 16.1中,您将看到来自此代码片段的示例以及改变子细分的两个额外示例:
#Define the configuration of the image.
canvas_size = (500, 500)
numberSubdivisions = 4
- 对于镶嵌,我们需要定义黄金比例。
黄金比例也被称为黄金平均或神圣比例(还有其他名称)。该比例约为 1.618。例如,如果我们谈论将一条线段分成两部分,那么较大部分的长度除以较小部分的长度将等于两部分之和除以较大部分的长度:。对于镶嵌,我们需要定义这个黄金比例。
看一下以下代码片段:
#Define the Golden Ratio - gr
gr = (1 + math.sqrt(5)) / 2
- 现在我们使用函数来定义当我们的三角形进行子细分时会发生什么:
def subdivide(triangles):
result = []
for color, A, B, C in triangles:
if color == 0:
P = A + (B - A) / gr
result += [(0, C, P, B), (1, P, C, A)]
else:
Q = B + (A - B) / gr
R = B + (C - B) / gr
result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
return result
在上述代码中,我们定义了将三角形细分的函数。该函数包含一个条件语句,用于在找到比例之前识别三角形的颜色。
- 要创建三角形的轮廓,我们需要将三角形附加到一个组中。因为 Python 是一种面向对象的编程语言,所以我们可以通过创建一个空列表,然后使用循环附加形状来轻松地做到这一点:
#Wheel of teal triangles
triangles = []
for i in range(10):
B = cmath.rect(1, (2*i - 1) * math.pi / 10)
C = cmath.rect(1, (2*i + 1) * math.pi / 10)
if i % 2 == 0:
B, C = C, B # Make sure to mirror every second triangle
triangles.append((0, 0j, B, C))
for i in range(numberSubdivisions):
triangles = subdivide(triangles)
- 现在我们要准备用于我们的镶嵌的画布。请注意,我们使用
cairo
函数来使用我们在算法开头定义的变量来定义参数。在这里,我们使用canvas_size
:
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, canvas_size[0], canvas_size[1])
cr = cairo.Context(surface)
cr.translate(canvas_size[0] / 2.0, canvas_size[1] / 2.0)
wheelRadius = 1.2 * math.sqrt((canvas_size[0] / 2.0) ** 2 + (canvas_size[1] / 2.0) ** 2)
cr.scale(wheelRadius, wheelRadius)
- 现在我们要定义我们将使用的两个三角形。在这种情况下,我们的三角形是蓝绿色和紫色的,但是您可以更改它们的 RGB 值,也就是说,如果您想要测试不同的颜色:
#Define the teal triangles
for color, A, B, C in triangles:
if color == 0:
cr.move_to(A.real, A.imag)
cr.line_to(B.real, B.imag)
cr.line_to(C.real, C.imag)
cr.close_path()
cr.set_source_rgb(.2, .8, .8)
cr.fill()
#Define the purple triangles
for color, A, B, C in triangles:
if color == 1:
cr.move_to(A.real, A.imag)
cr.line_to(B.real, B.imag)
cr.line_to(C.real, C.imag)
cr.close_path()
cr.set_source_rgb(0.7, 0, 0.7)
cr.fill()
color, A, B, C = triangles[0]
cr.set_line_width(abs(B - A) / 10.0)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
上述代码创建了蓝绿色的三角形和紫色的三角形。每个都使用 RGB 值定义,并使用路径和线条创建。
- 旋转的三角形形成了一个瓷砖图案,也就是我们的镶嵌图案。瓷砖之间也有边框分隔。边框的颜色也在接下来的循环中定义:
#Triangle borders
for color, A, B, C in triangles:
cr.move_to(C.real, C.imag)
cr.line_to(A.real, A.imag)
cr.line_to(B.real, B.imag)
cr.set_source_rgb(0.3, 0.5, 0.3)
cr.stroke()
- 最后,我们希望算法创建一个带有我们的镶嵌的图像文件:
surface.write_to_png('tessellation.png')
以下截图显示了使用不同数量的子区划进行的三种变化:
图 16.1 – 样本镶嵌
正如您从前面的图像中所看到的,子区划的数量越多,瓷砖图案就会变得越小,以适应我们定义的画布大小。
当您使用算法时,考虑更改画布大小、子区划和颜色。如果您想要额外的挑战,尝试将三角形图案更改为其他多边形。
问题 2 – 在生物数据分析中使用 Python
对于这个特定的问题,我们将使用Breast_cancer_data.csv
文件,该文件可以在Kaggle上找到(www.kaggle.com/nsaravana/breast-cancer?select=breast-cancer.csv
)。该文件也已上传到本书的 GitHub 存储库中。
当查看数据时,有时我们想要与我们当前拥有的数据进行比较,或者我们想要在机器学习中使用它进行预测。在这种情况下,我们将看看如何使用数据集中两个特定列的值来呈现另一种类型的图表,即散点图。
假设您收到了这些数据,并且已经确定您的平均周长和平均纹理比列中的其他值更好。您现在的目标是创建一个算法,通过比较这两列的值来分析这两列的值,使用散点图。我们的目标只是获得那个散点图。对于额外的分析和机器学习应用,可以自由探索[第十三章],使用分类和聚类,和[第十四章],在统计分析中使用计算思维和 Python,以获取额外的帮助。
这个问题的完整代码可以在文件ch16_BreastCancerSample.py
中找到。我们现在可以开始设计我们的算法:
- 我们像往常一样从数据开始,导入我们将使用的库。请注意,我们在这里使用了两个显示库,即
matplotlib
和seaborn
库。这是我们第一次使用seaborn
。我们使用seaborn
是因为使用这个库可以轻松处理额外的工作,比如找到回归线:
#Import libraries needed
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
- 现在我们要找到
.csv
文件。请记住,您可以先建立目录。否则,请确保您在运行文件之前包含文件的完整位置。由于我们的目录不同,请确保在运行文件之前更改目录:
#Get data set. Remember to check your directory and/or add the full location of the file.
dataset = pd.read_csv('C:\\... \\breast-cancer.csv')
dataset.head()
dataset.isnull().sum()
注意算法中的dataset.head()
命令。如果我们仅运行代码到那一点,那么我们会得到以下输出:
图 16.2 - 显示标题数值的表格
dataset.isnull().sum()
命令帮助我们查看是否有空数据点或值。
- 如果有许多空值,我们可以在开始分析之前清理数据集。这个数据是干净的,可以从以下输出中看到,如果我们运行程序直到
dataset.isnull().sum()
,如下屏幕截图所示:
图 16.3 - 空值检查的输出
- 由于没有缺失值,如前面的屏幕截图所示,我们将继续到下一个代码片段,我们将为诊断创建
count
变量:
#Create count variable for diagnosis
count = dataset.diagnosis.value_counts()
count
在前面的代码片段中创建了count
变量,这意味着我们可以使用诊断的值来创建条形图,无论是恶性还是良性。
- 以下代码片段创建了该条形图并显示了结果输出:
#Create bargraph of the diagnosis values
count.plot(kind = 'bar')
plt.title('Tumor distribution (malignant: 1, benign: 0)')
plt.xlabel('Diagnosis')
plt.ylabel('count')
plt.show()
看一下下面的屏幕截图,显示了使用诊断数值的条形图。正如你所看到的,条形图显示了恶性肿瘤与良性肿瘤的数量:
图 16.4 - 恶性与良性诊断条形图
现在我们有了这些信息和条形图,我们可以开始使用数据集中的数值来查看其他组合和比较。
- 您可以运行不同的分析来查看哪些更相关,但现在,我们只是要使用周长均值和纹理均值来创建我们的散点图。以下代码片段显示了如何使用
seaborn
库来创建这些:
y_target = dataset['diagnosis']
dataset.columns.values
dataset['target'] = dataset['diagnosis'].map({0:'B',1:'M'})
#Create scatterplot of mean perimeter and mean texture
sns.scatterplot(x = 'perimeter_mean', y = 'texture_mean', data = dataset, hue = 'diagnosis', palette = 'bright');
创建了我们的散点图后,算法将返回以下输出,显示了平均周长散点图与平均纹理散点图的比较:
图 16.5 - 平均周长与平均纹理散点图
我们将在这里暂停数据分析。但是,请注意,您可以将此示例推进得更远。事实上,您可以在 Kaggle 中找到对这个特定数据集进行的多个应用和分析,以及一些开发人员和编码人员如何整合机器学习以进行预测。生物信息学的世界是广阔的,数据科学应用也在不断增长。在这些问题中使用 Python 是有帮助的,因为它易于使用并且有适用的库。
问题 3 - 使用 Python 分析特定人群的数据
对于本节,我们将以这种方式陈述我们的问题 - 现在是 2020 年,世界被一种名为SARS-COV-19的病毒,也称为冠状病毒或COVID-19,的大流行所压倒。数据是广泛可用的,我们试图看看特定位置发生了什么情况,特别是该位置的死亡人数是如何增长的。我们找到了纽约时报的 GitHub 存储库,其中包含 COVID-19 数据,并下载了每日更新的主数据。让我们看看我们需要做什么以及我们如何找到它。
定义要分析和识别人群的具体问题
这个问题很广泛。*太广泛了!*因此,让我们首先看一个地点,仅限一个月。例如,让我们选择波多黎各和十月份。从主.csv
文件中,我们只提取了波多黎各特定的数据,并将其添加到我们的存储库中。同样,可以在纽约时报的 covid-19-data 存储库中找到主数据,并且您可以使用完整数据、特定州甚至特定县进行多种分析。
现在,我们将集中于创建一个关于 2020 年 10 月波多黎各特定死亡数据的可视化。仅仅通过查看数据,我们就可以看到死亡人数正在上升。请看下面的截图:
图 16.6 - 2020 年 10 月波多黎各前 20 天的数据
从前面的截图可以看出,死亡列继续上升,病例数量也是如此,我们稍后将在本问题中再次查看。
虽然以表格格式读取的数据可能有所帮助,但如果我们要呈现这些信息,特别是如果我们想要识别趋势并影响政策变化,那么可视化表示就至关重要。因此,让我们看看如何为这些特定数据创建散点图。完整文件可以在ch16_CovidPR.py
中找到:
- 与数据一起工作时,我们需要确保导入我们将使用的库:
import pandas as pd
import matplotlib.pyplot as plt
- 接下来,我们需要获取我们的文件。请记住,有多种方法可以做到这一点。您可以给 Python 提供文件的完整位置,或者您可以首先识别目录,然后只提供文件名。在运行程序之前,请确保更改您将使用的
.csv
文件的位置:
df = pd.read_csv('C:\\...\\us-PuertoRico.csv')
- 在确定文件后,我们将只需使用日期创建一个简单的散点图作为我们的x轴,以及死亡人数作为我们的y轴。此代码片段中的下几个命令是为了使图表更易于阅读,例如y轴标签,x-tick标记的旋转以及图表的标题。x-tick标记是水平轴或x轴的分割标记。您可以在图 16.6中看到x-tick标记和标签:
plt.scatter(df['date'], df['deaths'])
plt.xticks(rotation = 90)
plt.title('Deaths per day during October 2020 due to COVID19 in Puerto Rico')
plt.ylabel('Number of Deaths')
fig.tight_layout()
plt.show()
plt.savefig('COVID_PR.png')
从前面的代码片段可以看出,我们还创建了一个图像文件,以备将来使用。该图表将显示在我们的屏幕上,如下截图所示:
图 16.7 - 2020 年 10 月波多黎各每日死亡人数由于 COVID-19
这是一个有用的图表,可以看出死亡人数正在稳步增加。我们还可以做更多的事情,比如尝试确定回归,这是我们可以使用 Python 的numpy
库做的另一个功能,欢迎您去尝试!
现在,我们将看一下按日期分类的病例。代码与以前相同,只是我们的y轴和标题将不同。完整代码可以在ch16_CovidPR_2
文件中找到。由于代码非常相似,我们在这里不分享它。但是,我们的结果图可以在下面的截图中看到:
图 16.8 - 2020 年 10 月波多黎各 COVID-19 每日病例
从前面的截图中可以看出,波多黎各的病例数量每天都在上升。我们可以对这两个图表做很多事情;分析它们的回归,通过查看其他月度数据来验证额外的趋势等等。您已经看到如何根据您的.csv
文件创建一个简单的图表来显示数据;其余的就取决于您了。我们将在下一节中研究一个新问题。
问题 4 - 使用 Python 创建房屋数据模型
让我们来看一个问题,我们想要显示布鲁克林,纽约的房地产市场的趋势和信息。数据集包括 2003 年至 2017 年纽约市房屋销售数据的信息。使用的数据集已经以可用格式合并,并且可以在 Kaggle 上找到(www.kaggle.com/tianhwu/brooklynhomes2003to2017
)。此外,.csv
文件的副本可以在 GitHub 存储库中找到,文件名为brooklyn_sales_map.csv
。
定义问题
对于这个特定的问题,我们有一个庞大的数据文件。我们可以按社区查看信息,按年份比较销售价格,将建造年份与社区进行比较以找到趋势、历史等等。我们可以花费数小时、数天、数周来处理这个数据集。所以让我们尝试把精力集中在这个例子中要完成的事情上。为此,我们将创建两个可视化模型。第一个是根据销售年份的房屋百分比的水平条形图。第二个是显示房屋销售地点的价格范围的条形图。
水平条形图可以帮助更清晰地显示数据,以便我们可以看到房屋价格范围以及是否有显著变化。垂直条形图可以按社区显示相同的价格范围,因此我们可以看到根据房屋销售地点的不同是否有显著变化。
算法和数据的可视化表示
让我们看一下代码片段。完整的文件可以在 GitHub 存储库的ch16_housingBrooklyn.py
下找到。和往常一样,在尝试运行程序之前不要忘记更新文件位置:
- 对于这个特定的程序,我们需要
pandas
和matplotlib
库,所以我们需要导入它们:
import pandas as pd
import matplotlib.pyplot as plt
- 接下来,我们需要读取我们的文件。这就是你需要更新这段代码以便在你的机器上运行它的地方:
df = pd.read_csv('C:\\...\\brooklyn_sales_map.csv')
- 现在我们要创建我们的
bins
。这些是我们的值范围,当我们创建图表时会用到它们,你可以在以下几行代码中的df['price_range']
下看到它们:
bins = [-100000000,20000,40000,60000,80000,100000,1000000,10000000,500000000]
ranges_prices = ['$0-$200k','$200k-$400k','$400k-$600k','$600k-$800k','$800k-$1mlln','$1mlln-$10mlln','$10mlln-$100mlln','$100mlln-$500mlln']
df['price_range'] = pd.cut(df['sale_price'], bins = bins, labels = ranges_prices)
- 现在我们要定义一个函数,我们将在其中转换一些数据。请注意,我们在数据集的每一年上运行该函数,以找到我们稍后将用于
housing_df
的总百分比:
def convert(year):
return df[df['year_of_sale'] == year].groupby('price_range').size()
percent_total = [x/sum(x)*100 for x in [convert(2003),convert(2004),convert(2005),convert(2006),convert(2007),convert(2008),convert(2009),convert(2010),convert(2011),convert(2012),convert(2013),convert(2014),convert(2015),convert(2016),convert(2017)]]
year_names = list(range(2003,2018))
housing_df = pd.DataFrame(percent_total, index = year_names)
ax_two = housing_df.plot(kind = 'barh', stacked = True, width = 0.9, cmap = 'Spectral')
plt.legend(bbox_to_anchor = (1.45, 1), loc='upper right')
ax_two.set_xlabel('Percentages', fontname='Arial', fontsize = 12)
ax_two.set_ylabel('Years', fontname='Arial', fontsize = 12)
ax_two.set_title('Housing Sale ')
前面的片段帮助我们创建了两个模型中的第一个。这是一个水平条形图。我们标记了所有的轴和图表,然后在前面的代码片段中显示的下一行中,我们还定义了我们将在这个图表中使用的颜色映射,这里是'Spectral'
。你可以尝试不同的颜色映射以便更容易阅读。看一下我们的第一个图表,如下所示:
图 16.9-布鲁克林每年的房屋销售
请注意我们在前面的截图中使用了百分比。这使我们能够显示每个价格范围的销售额占比,但它并没有显示每个价格范围的实际销售数量。这两件事情是完全不同的。在这里,我们正在寻找趋势。销售额高于 100 万美元的百分比在 2008 年到 2009 年略微下降后一直在增加。在 2017 年,高于这个价格点的销售额比 2003 年要高得多。
但这是总销售额。如果我们只看这张图而不看数字,我们就不知道例如 2017 年总共卖出了更少的房子。再次,需要注意的重点是,这张图对于理解每个价格范围内房屋销售份额非常有帮助,但这就是这张图给我们的全部信息。现在让我们看看文件中剩下的代码。
- 在下面的片段中,我们创建了我们的第二张图,它使用我们的信息为每个社区的每个价格范围生成了一个垂直条形图的百分比:
df.groupby(['neighborhood','price_range']).size().unstack().plot.bar(stacked = True, cmap = 'rainbow')
plt.legend(bbox_to_anchor = (1.45, 1), loc = 'upper right')
plt.title('Pricing by Neighborhoods in Brooklyn from 2003 to 2017')
plt.ylabel('Price Range')
plt.xticks(fontsize = 6)
这张图用条形图显示了每个社区的价格范围。让我们看一下以下截图中的第二张图:
图 16.10-布鲁克林 2003-2017 年各社区的定价
正如你从前面的截图中所看到的,我们得到了一些比图 16.1中提供的更详细或更详细的重要信息。在这种情况下,数据是由邻里提供的,并且该数据是按那些邻里的价格范围进行了分类。
当我们查看大型数据集时,我们可以创建多个不同的模型,甚至使用它们来预测未来的值。查看.csv
文件中可用的数据,并尝试使用其他数据创建一些不同的表示,例如商业与住宅销售、税务分类等。
问题 5 – 使用 Python 创建电场线图
在本节中,让我们看一下 Python 在工程和特别是物理学中的一些应用。我们将创建一个电场线图。为什么我们要创建这种类型的图,并且它到底是什么? 当存在电荷时,就会产生电场。我们使用矢量来显示空间中每一点的电场。在物理学中,电场是单位电荷的电力。看一下正点电荷和负点电荷的电场是什么样子,如下图所示:
图 16.11 – 电场示例
正如你所看到的,电场线将从电荷开始或结束于电荷。如果它从电荷开始,它是正的,而如果它结束于电荷,它是负的,正如前面的截图所示。较小的电荷会有较少的线条,而较大的电荷会有更多的线条。此外,对于较大的电荷,线条会比较接近,而对于较小的电荷,线条会比较远。
对于我们的问题,我们想要为任意数量的电荷创建一个电场线图。让我们看看下面的代码文件中会是什么样子。请注意,我们已经将代码分解以解释各个部分,但完整的文件可以在 GitHub 存储库中找到:
- 像往常一样,首先,我们将导入必要的库:
ch16_electricFieldLines.py
import numpy as np
import matplotlib.pyplot as plt
import random
- 然后,我们将通过定义一些变量来设置我们的x和y轴:
np.seterr(divide='ignore', invalid='ignore')
#Define the size of the electric field lines grid
N = 20
M = 25
#Set the x and y coordinates
x_coor = np.arange(0, M, 1)
y_coor = np.arange(0, N, 1)
x_coor, y_coor = np.meshgrid(x_coor, y_coor)
E_x = np.zeros((N, M))
E_y = np.zeros((N, M))
在前面的代码片段中,我们致力于定义我们的网格并设置坐标。然后我们创建了网格。meshgrid()
函数从向量坐标返回坐标矩阵。
- 在设置了我们的坐标并设置了我们的网格之后,我们可以开始定义我们的电荷发生了什么。首先,我们需要确定将绘制多少个电荷:
#Set the number of total charges to plot
nq = 8
#Create empty lists to store coordinates of charges
qq = [[], []]
for dummy in range(nq):
q = random.choice([-1, 1])
q_x, q_y = random.randrange(1, N), random.randrange(1, M)
qq[0].append(q_y)
qq[1].append(q_x)
for i in range(N):
for j in range(M):
denom = ((i - q_x) ** 2 + (j - q_y) ** 2) ** 1.5
if denom != 0:
E_x[i, j] += q * (j - q_y) / denom
E_y[i, j] += q * (i - q_x) / denom
正如你从前面的代码片段中所看到的,我们确定了电荷的数量后,创建了两个空列表。然后我们使用嵌套的for
循环根据电荷的数量向这些列表添加坐标。
- 在我们进行必要的数学运算以获得我们的坐标和向量之后,我们可以继续绘制我们的电场线图。我们将使用
quiver
图,这是一个我们可以用来创建矢量场的matplotlib
图形:
C = np.hypot(E_x, E_y)
E = (E_x ** 2 + E_y ** 2) ** .5
E_x = E_x / E
E_y = E_y / E
plt.figure(figsize=(12, 8))
#Plot charges
plt.plot(*qq, 'ms')
#Create 2D array
rr = plt.quiver(x_coor, y_coor, E_x, E_y, C, pivot='middle')
cbar = plt.colorbar()
cbar.ax.set_ylabel('Magnitude')
#Label graph
plt.title('Electric Field Lines in Python')
plt.axis('equal')
plt.axis('off')
plt.show()
重要的是要始终为我们的图表和绘图添加标签,因为这将使信息更易读,特别是对于那些不知道这背后的代码意味着什么或图表和绘图代表什么的人。当我们运行我们的代码片段时,我们会得到以下输出:
图 16.12 – 八个电荷的电场线
正如你在我们的图中所看到的,有正电荷和负电荷。看一下图中右下角的电荷。该电荷是负的,因为箭头指向电荷。最左边的电荷,已经放大,如下所示,显示为正电荷,因为箭头指向远离电荷的方向:
图 16.13 – 放大的正电荷
- 让我们来看最后一个图形,如下截图所示,其中有一个具有三个电荷的电场线图:
图 16.14 - 具有三个电荷的电场线
正如您所看到的,这个特定的图包含两个正电荷和一个负电荷。如果我们再次运行,我们可能会得到三个正电荷,例如,因为每次运行算法时,我们都会得到一个新的表示,其中正电荷和负电荷的值是随机的。
这种类型的领域以及学习如何使用矢量和箭头图可以帮助我们表示大量信息。在电场线的情况下,我们可以通过简单的视觉图了解电荷的方向和强度。
看看 GitHub 存储库中的代码片段,并尝试更改一些参数,例如图的大小和电荷的数量。通过这些图的实践和改变一些参数,您可以更容易地创建它们。
问题 6 - 使用 Python 分析基因数据
让我们把注意力转移到查看一个更大的数据集。您正在处理实验室小鼠的数据,并获得了三体小鼠和这些小鼠的蛋白质表达的数据。由于数据量巨大,我们从 Kaggle 的公共领域文件中截取了一些数据。我们只关注这些小鼠的六种蛋白质表达,并且再次只关注研究中的三体(唐氏综合征)小鼠。完整的文件可以在 Kaggle 上找到www.kaggle.com/ruslankl/mice-protein-expression
。截断文件可以在我们的 GitHub 存储库中找到。
假设您不知道从这些数据的哪里开始。*您甚至应该看什么?*嗯,这通常是数据科学中遇到的第一件事。我们并不总是能参与研究设计或数据收集。很多时候,我们会收到大量的数据文件,并需要找出要查找的内容,如何解决问题,无论我们决定的问题是什么,以及如何以最佳方式显示信息。
另外,这是提醒您在尝试运行此程序之前更改文件位置的提示。这个非常简单的程序可以在ch16_pairplots.py
文件中找到。让我们从算法开始:
seaborn
库可以帮助我们一点,让我们开始。我们可以创建pairplot()
,它将使用直方图和散点图相关联.csv
文件中的数值数据。这有点像一个神奇的魔术。我们可以用两行代码来看看我们看到了什么。看看生成图 16.7所需的两行代码(请注意,实际上有四行代码,但我没有计算我用来导入库的两行代码):
import seaborn as sns
import pandas as pd
df = pd.read_csv('C:\\...\\Data_Cortex_Nuclear.csv')
sns.pairplot(df, hue = 'Treatment')
当您运行此程序时,请耐心等待。算法可能很简单,但它在后台所做的事情并不简单。请看以下截图以查看我们的配对图:
图 16.15 - 三体小鼠中蛋白质表达的配对图与治疗变量
请注意,我们的数据根据治疗方式分为两种颜色,分别是注射甲胺醇或盐水。从图中我们可以看到,一些蛋白质似乎比其他蛋白质具有更高的相关性。让我们暂停一下。
- 现在假设我们的目标不是根据治疗来检查表达,而是根据类别。然后我们可以运行代码,但首先我们将在算法中将色调更改为
class
。结果如下所示:
图 16.16 - 三体小鼠中蛋白质表达的配对图与类变量
请注意,这些图表非常相似。然而,这些图表之间的区别在于根据另一个特征来确定每个点的位置。例如,在class
变量图表中,我们有四种颜色,因为在我们的特定数据集中有四类老鼠。
它们分别是t-CS-s,指的是被刺激学习(电击)并注射生理盐水的老鼠;t-CS-m,指的是被刺激学习(电击)并注射美金刚烷的老鼠;t-SC-s,指的是未被刺激学习并注射生理盐水的老鼠;以及t-SC-m,指的是未被刺激学习并注射美金刚烷的老鼠。
通过查看我们的相关性,我们可以看到许多蛋白质之间存在强烈的正相关性,比如NR2A_N和BDNF_N。无论这是否相关,无论在我们的研究中是否重要,无论是否显著,这些都是我们在进行研究时需要考虑的事情。一旦我们看到图表,我们可以选择进一步探索信息。
在查看这个数据集时,另一种有用的图表类型是箱线图。我们可以使用箱线图来查看我们想更仔细观察的蛋白质的类别的蛋白质表达水平。让我们以NR2A_N
蛋白为例。使用seaborn
箱线图,我们可以使用ch16_boxplot.py
文件中的代码为这种特定蛋白创建一个图表。像往常一样,首先检查文件位置:
import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv('C:\\...\\Data_Cortex_Nuclear.csv')
protein = df[['NR2A_N', 'class']].dropna()
sns.boxplot(x='class', y='NR2A_N', data = protein)
plt.show
在上面的代码中,我们确定了我们想要比较的事物,即蛋白质和类别。然后我们将使用我们的seaborn
库创建箱线图,如下所示:
图 16.17 - 按类别绘制 NR2A_N 蛋白表达的箱线图
从图表中可以看出,我们的三体老鼠的分布因类别而异,未被刺激学习并注射生理盐水的老鼠在这种蛋白质的表达上显示出更广泛的范围。让我们尝试将这种蛋白质更改为数据集中的其他蛋白质之一,即ITSN1_N蛋白。以下截图显示了生成的箱线图:
图 16.18 - 按类别绘制 ITSN1_N 蛋白表达的箱线图
在这个特定的箱线图中,我们可以确定t-CS-m和t-SC-m类别中的异常值,即注射了美金刚烷的两类老鼠。这可能告诉我们需要进一步了解美金刚烷注射与该特定蛋白之间的任何关系。如果我们要观察非三体老鼠,如果其他因素相同,这些数据(范围)是否适用于该蛋白? 这些是我们在观察这类数据集时会问自己的一些问题。
如果你记得的话,计算思维过程很少是一条直线。如果我们确定了我们想要在算法中考虑的事情,我们不会只是让我们的算法独自运行并决定它已经完成了,所以我们不会对它进行更改。我们会回去重新确定我们需要什么,对我们的设计进行必要的更改,然后再次创建我们的算法。这更接近于当我们处理更大的数据集时会发生的情况。我们会查看一些初始的可视化,也许创建一些不同类型的图表,运行一些统计分析,然后决定下一步该如何处理数据。这只是 Python 可能实现的一小部分。
问题 7 - 使用 Python 分析股票
是时候玩一些股票了。您可以通过Quandl访问大量数据,该平台允许免费使用 API 进行教育用途。也有一些高级数据集可用。我们将坚持教育目的,所以这对我们的要求应该足够了。
在这个问题中,我们将学习如何从 Quandl 获取数据并查看 VZ 股票价格。VZ是Verizon股票价格的代码。我们将使用它们来使用quandl
来预测价格,这是 Python 的一个包,也是一个充满有用信息的网站。让我们来看一下我们如何获取我们想要的信息。完整的代码,除了 API 密钥,可以在我们的存储库中的ch16_stockAnalysis.py
文件中找到:
- 让我们来看一下如何导入数据。你需要自己的 API。如果你想检查另一个股票,比如
AMZN
,你可以用'WIKI/AMZ'
替换'EOD/VZ'
。AMZN是Amazon股票的代码。但让我们来看看我们的VZ
集:
import quandl
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
from sklearn.model_selection import train_test_split
#Get data from Quandl. Note that you'll need your own API to substitute in the api.key below.
quandl.ApiConfig.api_key = '…'
VZ = quandl.get('EOD/VZ')
print(VZ.head())
当我们运行上述代码时,我们得到了数据集中前五个值的表格。以下截图显示了我们的数值表:
图 16.19 - EOD/VZ 股票表
- 现在假设我们只想关注调整后的收盘价,以便以后进行预测。那么我们可以使用以下代码片段来实现:
#Grab the Adj_Close column
VZ = VZ[['Adj_Close']]
print(VZ.head())
运行上述代码后,我们的调整后的表格如下所示:
图 16.20 - EOD/VZ 调整后的股票收盘价表
现在我们已经学会了获取当前数据,我们将现在使用我们放在 GitHub 存储库中的数据集来进行操作,以确保你能够复制结果。然后你可以尝试使用 Quandl API 来获取当前数据。
让我们来看一下文件名为VZ.csv
的数据集。这包含了 1983 年至 2020 年 4 月的 VZ 相同数据。*我们从这个数据集中想要什么?*我们想要做一些预测。所以让我们建立那个模型。
请注意,代码相当长,所以包含了你需要的所有内容的文件(除了第 15 行的文件位置,你需要添加),在ch16_predictionsModel.py
中。但让我们来看一下那个文件中的一些代码片段:
- 以下代码片段将为数据集创建一个图。它从文件中选择
Date
列并将其设置为索引值。然后它创建一个图并为图和坐标轴添加标签:
VZ["Date"]=pd.to_datetime(VZ.Date,format="%Y-%m-%d")
VZ.index=VZ['Date']
plt.figure(figsize=(16,8))
plt.plot(VZ["Close"],label='Close price history')
plt.title('Closing price over time', fontsize = 20)
plt.xlabel('Time', fontsize = 15)
plt.ylabel('Closing price', fontsize = 15)
plt.show()
我们还没有看到模型。我们还没有定义我们的训练数据。我们只是看了我们的股票价格从1983年到2020年的情况,如下所示。请注意,第一个刻度标签显示1984。我们的数据似乎是在1984年之前开始的。刻度标记是自 1980 年以来每 4 年一次,如下所示:
图 16.21 - VZ 股票随时间的收盘价
从前面的图表中可以看出,股票价格在任何情况下都不是线性的。它们上升、下降,然后再上升。一个预测模型将需要大量数据,以便我们能够准备出最佳的预测。我们的数据集有 9166 行数据。这将在下一步中派上用场。
- 让我们来看一下我们将要使用的另一段代码:
VZ3=VZ2.values
train_data=VZ3[0:750,:]
valid_data=VZ3[750:,:]
VZ2.index=VZ2.Date
VZ2.drop("Date",axis=1,inplace=True)
scaler=MinMaxScaler(feature_range=(0,1))
scaled_data=scaler.fit_transform(VZ2)
x_train_data,y_train_data=[],[]
for i in range(60,len(train_data)):
x_train_data.append(scaled_data[i-60:i,0])
y_train_data.append(scaled_data[i,0])
x_train_data,y_train_data=np.array(x_train_data),np.array(y_train_data)
x_train_data=np.reshape(x_train_data,(x_train_data.shape[0],x_train_data.shape[1],1))
请注意代码中train_data=VZ3[0:750,:]
行中的750
值。这意味着我只使用了数据的前 750 行,而不是可能的 9166 行数据来训练我的模型。这不太好。
让我们来看一下以下截图,显示了我们运行此预测模型时的结果。请注意,我们选择将原始信息复制到我们的图形中。Python 会指出这可能是我们想要修复的问题。这取决于你是否要这样做。目前,将原始数据作为我们图形的叠加提供了一个很好的视觉比较模型的方式:
图 16.22 - 收盘价预测
正如您在前面的图表中所看到的,这里显示的橙色部分是原始的、复制的值。绿色显示了我们模型的预测。它们并不糟糕,但也不如它们本应该的那样紧密。
- 让我们看看当我们使用 7500 行数据时会发生什么,这大约是可用数据的 82%。需要注意的是,GitHub 存储库中的文件使用了 7500 的值,但您可以随意更改和调整这些值,以便测试模型的准确性。以下图表显示了我们的结果:
图 16.23 - 使用 7500 行数据的预测模型
请注意,在这个模型中,真实线条和预测线条之间的距离更近了。这是因为我们用来训练模型的数据越多,我们的预测就会变得越好。
在我们离开这个例子之前,请注意我们并没有在这里涵盖代码文件的全部内容。本书的其他部分已经讨论了一些代码,因此我们专注于算法的新部分和对算法解决方案至关重要的部分。该代码文件的最后一部分确实使用了**长短期记忆(LSTM)**模型。LSTM 模型是一种人工循环神经网络。我们在机器学习中使用这个模型来创建深度学习模型。
*我们的模型实际上能预测股票的价格吗?*不行。否则,我们在市场上就会轻松很多。但模型确实可以非常准确地预测价格是否会上涨或下跌。
问题 8 - 使用 Python 创建卷积神经网络(CNN)
在这一部分,我们将研究一个使用人工智能(AI)的问题。更具体地说,我们将致力于创建一个卷积神经网络,或者CNN。*那么 CNN 是什么?*CNN 是一种深度学习算法。CNN 将图像作为输入。然后根据预定条件对图像进行处理和赋予重要性,这将帮助我们区分和分类图像。
以下图表说明了卷积神经网络所涉及的过程:
图 16.24 - 卷积神经网络过程
卷积神经网络(CNN)是为了简化我们对图像进行分类的方式而创建的,同时又不牺牲我们希望从图像分析中得到的准确性。这就好像我们在应用滤镜一样。一旦我们应用了滤镜,我们就可以看到特征。前面的图表显示了这个过程的简化示意图。
我们将要深入研究的问题是手写训练和分析。在考虑计算思维过程时,我们真正想要做的是尽可能准确地分析手写。为此,我们分析数百甚至数千张图像来创建和训练我们的模型。我们使用的图像越多,我们的模型就会越准确。
对于我们的模型,我们将使用一个包含 70,000 张图像的数据集。前 60,000 张图像用于训练,而我们将使用其他 10,000 张进行测试。完整的代码可以在ch16_CNN_mnist.py
文件中找到。我们将看一些来自该代码的片段,并进行一些调整以展示额外的组件。只要您安装了所有必要的库和软件包,就可以在 GitHub 存储库中运行文件而不进行更改。
让我们开始设计模型:
- 让我们首先看一段代码片段,它将上传数据集,然后显示数据集中的第一项:
from keras.datasets import mnist
#Grab the testing and training data from existing dataset
(X_train, y_train), (X_test, y_test) = mnist.load_data()
import matplotlib.pyplot as plt
#Take a look at the first item in the dataset
plt.imshow(X_train[0], cmap='Greys')
我们在训练集中使用索引0
来查看第一幅图像。cmap
属性将使色图变为灰色。您可以根据需要进行调整。顺便说一句,对于那些有困难看到颜色或有特殊颜色需求的人来说,改变色图可以显著改变图像的感知方式。让我们看一下以下的第一幅图像:
图 16.25 - MNIST 训练集中的第一幅图像
从前面的截图中可以看出,这是一个手写样本,很可能是数字5。我们可以多次运行程序,使用不同的索引来查看数据集中的其他样本。以下截图显示了其中一些样本及其相应的索引:
图 16.26 - 数据集中的样本图像按索引
我们使用的数据不是定量的,而是定性的。我们正在查看图像,因此我们需要一个可以分析这些图像的过程。为此,我们使用One-Hot 编码,它用新的二进制变量替换整数编码变量。
- 现在我们已经看了我们正在使用的东西,让我们使用以下代码片段来重塑和编码我们的模型。作为提醒,完整的代码可以在存储库中找到,但是一些组件会略有不同(例如我们的文件不会测试数据集中的图像):
#Reshape the model
X_train = X_train.reshape(60000,28,28,1)
X_test = X_test.reshape(10000,28,28,1)
from keras.utils import to_categorical
#Use One-Hot encoding
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
y_train[0]
如您所见,我们正在将图像分成训练集和测试集。然后对它们进行编码。
- 一旦我们进行了一次性编码,我们就可以创建我们的模型:
#Creating the model
from keras.models import Sequential
from keras.layers import Dense, Conv2D, Flatten
model = Sequential()
model.add(Conv2D(64, kernel_size=3, activation='relu', input_shape=(28,28,1)))
model.add(Conv2D(32, kernel_size=3, activation='relu'))
model.add(Flatten())
model.add(Dense(10, activation='softmax'))
#Compile the model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
在前面的代码片段中,我们使用了softmax
函数。softmax
函数有时被称为归一化指数函数。我们用它来规范化输出。
- 现在让我们训练模型。我们将首先拟合模型,然后验证数据。看一下这段代码:
#train the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=3)
这就是训练和测试的好处之一。也就是说,当我们理解并练习它们时,我们意识到只需要几行代码就可以做一些非常了不起的事情。前面的两行代码(第三行是注释)将产生一些很棒的事情,并允许我们的算法测试其他图像。
- 现在我们可以预测数据集中的图像。我们将从最后四个开始,因为每个人都从相同的四个数字开始,所以这次我想从后面开始。请注意,要有耐心。在这个算法中有成千上万的图像需要处理。当时代运行时,您将看到一个时钟,它会告诉您信息处理需要多长时间。对于这个特定的算法,只需要几分钟。让我们看一下我们需要运行预测的代码片段:
#Predict last 4 images
model.predict(X_test[9996:])
当我们运行这段代码时,我们得到了一系列相当复杂的数字。看一下以下的截图。我们已经突出显示了讨论的代码的关键部分:
图 16.27 - CNN 图像的模型预测
所以,我可以告诉你,第一个预测的数字是 3。*我们怎么知道这个数字代表 3 呢?*因为每个列表代表数字 0 到 9。所以想象一下,用[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]来替换第一个列表。因此,如果我们把这个看作索引,以01结尾的数字(在前面的截图中突出显示)是索引 3,也就是数字 3。所以我们的数字是 3、4、5 和 6。
- *但你相信这个模型吗?*我们可以直接返回到这次讨论开始时的代码片段,并打印我们的结果。记得稍微修改代码以打印测试图像,而不是训练图像,就像以下的代码片段中所示:
plt.imshow(X_test[9996], cmap='Greys')
在运行代码时,请记住你需要为每个索引运行代码以查看图像。以下截图显示了测试图像的每个相关索引的图像:
图 16.28 – 测试数据验证图像
正如你所看到的,我们的模型预测了这些索引中每个手写数字图像的正确值。
在结束讨论之前,重要的是要注意,这些模型目前在网站上被广泛使用,用于验证访问网站的访客是人类还是机器人。一些网站会有CAPTCHA,有时会提供手写字母,用户必须识别才能继续。这些 CAPTCHA 通常也使用深度学习。CNN 和这些模型的应用是无穷无尽的。
总结
在本章中,我们能够更深入地探讨计算思维的更多主题,特别是在处理数据和深度学习方面,使用 Python 编程语言。我们学会了如何创建 pairplots 来确定数据集中变量之间的关系。我们还学会了如何生成各种类型的图表来直观地表示我们的数据集。我们还学会了如何使用 Python 创建电场线。简而言之,我们应用了我们在之前章节中学到的知识,并在解决实际问题时扩展了我们的知识。
这本书的真正目的是:展示 Python 应用的广泛性,同时关注上下文中的真实问题。*我们是否涵盖了 Python 的所有功能?*这几乎是不可能的,因为 Python 的能力不断增长,这是因为它易于使用,易于学习,并且由于其开源性质,不断增加了许多应用程序。希望你能够使用一些新的脚本,了解一些你尚未探索的功能和能力,并享受探索这些场景。
*我们是否会有能力说我们已经创造了完美的算法?*我们这本书的作者认为不会。原因是我们总是在思考如何改进。我们总是质疑额外的应用。我们总是想要使它们更加高效。这正是计算思维帮助我们做的。我们可以分析、设计、测试、回顾,看我们是否达到了想要的目标,然后进行改进、重新设计、进行测试,然后重复。
希望在本章之后,你有机会练习并了解更多关于 Python 的能力。希望在阅读本书之后,你有机会了解编程中计算思维的重要性。感谢你加入我们的旅程!