书名:深入浅出数据科学:Python编程
ISBN:978-7-115-63622-5
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [美]布拉德福德·塔克菲尔德(Bradford Tuckfield)
译 殷海英
责任编辑 李 瑾
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright © 2023 by Bradford Tuckfield. Title of English-language original: Dive Into Data Science: Use Python to Tackle Your Toughest Business Challenges, ISBN 9781718502888, published by No Starch Press Inc. 245 8th Street, San Francisco, California United States 94103. The Simplified Chinese-edition Copyright © 2025 by Posts and Telecom Press Co., Ltd under license by No Starch Press Inc. All rights reserved.
本书简体中文版由美国No Starch出版社授权人民邮电出版社出版。未经出版者书面许可,不得复制或抄袭本书任何部分的内容。
版权所有,侵权必究。
本书通过对数据科学技术基本技能和丰富实用的示例的介绍,展示如何获取、分析和可视化数据,利用数据应对常见的业务挑战。通过优化共享单车公司的业务运营、从网站上提取数据并创建推荐系统等示例,你将学会如何找到数据驱动的解决方案并使用这些方案做出商业决策。本书所涵盖的内容包括进行探索性数据分析、运行A/B测试、使用逻辑回归模型进行二分类及使用机器学习算法等。通过本书,你还将学习如何预测客户需求、优化营销活动、减少客户流失、预测网站流量,以及构建推荐系统等。
只要对Python和高中数学有基本了解,你就可以毫不费力地阅读本书,并在日常工作中应用数据科学。本书适合有志成为数据科学家的人、数据科学相关的专业人士和数据科学爱好者阅读,也适合作为本科阶段数据科学入门课程的教材。
布拉德福德·塔克菲尔德(Bradford Tuckfield)是一位数据科学家、数据科学顾问和作家。他毕业于美国宾夕法尼亚大学沃顿商学院,获得运营与信息管理博士学位,并在杨百翰大学获得数学学士学位。他是Dive Into Algorithms(No Starch 出版社,2021年)的作者,也是Applied Unsupervised Learning with R(Packt出版社,2019年)的合著者。除了在知名金融公司和初创企业担任数据科学家和技术经理,他还在数学、商业管理和医学等领域的学术期刊上发表过他的研究成果。
作为加拿大统计局的首席数据科学家,Christian Ritter在从零开始建立该机构的数据科学部门方面提供了关键支持,包括开发数据分析平台。他领导了多个项目,利用自然语言处理、计算机视觉和推荐系统为不同的客户提供服务。Christian目前负责领导该机构对MLOps进行整合。此外,他还是OptimizeAI Consulting的创始人,并兼任独立数据科学顾问。在不进行数据科学项目时,他作为数据科学项目的研究生导师指导学生工作和学习。Christian拥有计算天体物理学博士学位。
许多人为本书的创作贡献了宝贵的力量。专业的导师和同事们在我学习Python、数据科学、商业知识,以及如何将它们结合起来的过程中给予了我帮助,他们包括Seshu Edala和Sundaram Narayanan博士等。在写作过程中,我的朋友们,如Sheng Lee、Ben Brown、Ee Chien Chua和Drew Durtschi等,给予了我宝贵的建议并且鼓励我。No Starch 出版社的Alex Freed在本书的整个创作过程中为我提供了大力的支持。作为技术审稿人,Christian Ritter为本书提供了非常好的建议和修正意见。在编辑过程中,Emma Tuckfield也提供了极佳的帮助。第8章的大部分代码和数据得益于Jayesh Thorat的协助。我深爱的祖母Virgie Day博士在我成长的整个过程中都给予了我鼓励,她还为第4章的一些思想提供了灵感,因此,我特别将这一章献给她。最后,我将本书献给Leah,一直以来她都是我最重要的支柱和动力来源。
几年前,谷歌的首席经济学家哈尔·瓦里安(Hal Varian)信心满满地宣称:“未来10年最炫酷的职业将是统计学家。”在他提出这一观点后的几年里,发生了两件事。第一,我们开始将统计学家称为数据科学家。第二,市场上对成熟的数据科学家的需求和其薪资水平都出现了巨大的增长。
然而,当今成熟的数据科学家的数量远远无法满足市场需求。因此,本书的目标是通过介绍当今世界知名公司使用的主流数据科学技术,来缓解这个问题。本书将详细解释每个示例代码,并阐述如何应用各种数据科学方法,以及如何为具有挑战性的解决方案提供创造性的思路和想法。本书旨在让所有读者具备成为数据科学家所需的技能,让读者轻松应对当今企业面临的最困难但也最令人兴奋的挑战。
数据科学不仅仅是一种职业技能,还是一个涵盖统计学、软件开发、数学、经济学和计算机科学等多个领域的广泛学科。通过数据科学,你可以分析数据、检测群体之间的差异、研究出现神秘现象的原因、对物种进行分类并进行实验。也就是说,数据科学可以帮助你进行许多科学研究。如果你对探索某些难以理解的事实真相感兴趣,或想更好地了解这个世界,那么你一定会对数据科学的这种能力感到兴奋。
简而言之,数据科学可以为几乎所有人提供一些价值。它可以帮助你解决业务问题,使你的业务完成得更加优秀。它可以让你更像一名科学家,让你能够更好地观察和清楚地了解你周围的世界。它还可以提高你的分析能力和编码技能。更重要的是,它非常有趣。成为一名数据科学家意味着你进入了一个不断增长和扩展的领域,意味着你每天都需要不断拓展自己的知识和技能。如果你认为学习一系列具有挑战性的新技能可以帮助你更好地工作、更好地思考,并获得一份“炫酷”的工作,那么请继续阅读本书。
虽然我们会用通俗易懂的语言来解释每个代码片段,以确保没有Python编程经验或编程经验较少的读者也能理解本书的内容,但你至少需要对编程的基础知识有一定的了解,例如变量赋值、for循环、if语句和函数调用等,这样才能更好地从本书中获益。
本书主要面向以下几类读者。
如今,似乎很多人想成为数据科学家,很多公司想聘用数据科学家。本书旨在帮助那些刚进入就业市场的求职者获得在数据科学领域工作所需的技能。同时,本书也可以帮助那些已经有其他工作,但想要“转换赛道”成为数据科学家,或者开始在当前职位上从事更多的数据科学工作的人。
许多专业人士,例如项目经理、高级管理人员、开发人员和一般的业务人员等,都可以从了解数据科学家的日常工作中受益。本书介绍的技能和知识可以帮助他们更加有效地与数据科学家合作。因此,本书对这些专业人士也是很有用的。
如今,我们生活在一个由数据组成的世界中,而数据科学是一个令人兴奋的领域,相信数据科学爱好者都会觉得本书极具魅力,并且具有很强的启发性。
本书适合作为本科阶段数据科学入门课程的教材,也适合对数据科学感兴趣的学生阅读。
本书讲述了世界知名公司的数据科学家经常使用的各种技术,还介绍了如何应用这些技术创造性地解决不同行业中的问题。下面简单介绍各章的内容。
第1章——探索性数据分析:解决数据科学问题的第一步是数据探索,包括在Python中读取数据、计算汇总统计信息、对数据进行可视化,以及发现一些常识性的见解等。
第2章——预测:主要介绍线性回归,线性回归是统计学中的一种常用技术,可以用来确定定量、变量之间的关系,甚至可以用来预测未来。
第3章——分组比较:主要介绍假设检验的探索和比较分组测量的标准统计方法。
第4章——A/B测试:讨论如何使用实验来确定哪种业务实践最有效。
第5章——二分类算法:介绍逻辑回归和线性概率模型等内容。
第6章——监督学习:深入探讨几种用于预测的机器学习方法,包括kNN、决策树、随机森林和神经网络等。
第7章——无监督学习:介绍无监督学习的基本知识及EM聚类,以及其他聚类方法与无监督学习的关系。
第8章——网络爬取:介绍从公开网站自动下载数据的方法以及正则表达式和Beautiful Soup等。
第9章——推荐系统:讨论如何建立一个自动向客户推荐商品的系统。
第10章——自然语言处理:探索一种将文本转换为可用于各种数据科学分析的定量向量的高级方法。
第11章——其他语言中的数据科学:介绍SQL和R这两种经常用于数据科学的语言。
我们会使用Python语言实现本书中介绍的算法。Python是一种免费、开源的语言,可以在所有主流平台上运行。如果你使用的是Windows、macOS或Linux系统,可以按照以下步骤来安装Python。
要在Windows系统中安装Python,请执行如下操作。
(1)打开针对Windows系统的Python 最新版本专用页面:https://www.python.org/downloads/ windows/(请确保包含最后一个正斜线)。
(2)单击要下载的Python版本的链接。要下载最新版本,请单击Latest Python 3 Release - Python 3.X.Y 链接,其中3.X.Y是最新的版本号,例如3.10.4。本书中的代码已经在Python 3.8上进行了测试,应该可以在更高版本中使用。如果需要下载旧版本,请将页面向下滚动到Stable Releases,找到你需要的版本即可。
(3)在第2步中单击链接会进入所选Python版本的专属页面。在文件列表中,单击Windows installer (64-bit)链接。
(4)单击第3步中的链接后将.exe文件下载到计算机中。该文件是一个安装程序文件,可通过双击来打开,它将自动执行安装过程。选中Add Python 3.X to PATH,其中的X是你下载的安装程序的版本号,比如10。之后,单击Install并选择默认选项。
(5)当你看到“Setup was successful”消息时,单击Close以完成安装过程。
现在,你的计算机上已经有了一个新的应用程序,它叫作Python 3.X。在Windows系统的搜索框中输入Python,然后单击出现的应用程序,将会打开一个Python控制台,你可以在Python控制台中输入Python命令,这些命令将在Python控制台中执行。
要在macOS中安装Python,请执行以下操作。
(1)打开针对macOS的Python最新版本专用页面:https://www.python.org/downloads/macos/(请确保包含最后一个正斜线)。
(2)单击要下载的Python版本的链接。要下载最新版本,请单击Latest Python 3 Release -Python 3.X.Y链接,其中3.X.Y是最新的版本号,例如3.10.4。本书中的代码已经在Python 3.8上进行了测试,应该可以在更高版本中使用。如果需要下载旧版本,请将页面向下滚动到Stable Releases,找到你需要的版本即可。
(3)在第2步中单击链接会进入所选Python版本的专属页面。在文件列表中,单击macOS 64-bit universal2 installer链接。
(4)单击第3步中的链接后将.pkg文件下载到计算机中。该文件是一个安装程序文件,可通过双击来打开。它将自动执行安装过程,选择默认选项即可。
(5)安装程序会在计算机上创建一个名为Python 3.X的文件夹,其中X是你安装的Python的版本号。在这个文件夹中,双击IDLE图标,打开Python 3.X.Y Shell,这里的3.X.Y是版本号。Python 3.X.Y Shell是一个Python控制台,你可以在其中运行任何Python命令。
要在Linux中安装Python,请执行如下操作。
(1)确定你使用的Linux系统的包管理器。两个常见的包管理器为YUM和APT。
(2)打开Linux控制台(也叫终端),执行以下两个命令:
> sudo apt-get update > sudo apt-get install python3.11
如果你使用的是YUM或其他包管理器,请将这两行中的apt-get替换为YUM或其他包管理器的名称。同样,如果你想安装其他版本的Python,请将3.11(编写本书时Python的最新版本号)替换为其他版本号,比如3.8(Python 3.8是用来测试本书代码的Python版本)。要查看最新版本的Python,请访问https://www.python.org/downloads/source/。在Latest Python 3 Release - Python 3.X.Y链接中,3.X.Y是版本号;如果使用上面的安装命令安装Python 3.11.3,那么对应的X和Y分别为11和3。
(3)在Linux控制台中执行下面的命令来运行Python:
> python3
在Linux控制台中,启动Python程序后,可以输入并运行Python命令。
当你按照“设置环境”中的步骤安装Python后,实际上安装了Python的标准库,也就是Python基础库。这个基础库是Python自带的,可以让你运行简单的Python代码,并且基础库中包含Python语言中的标准功能。虽然Python基础库的功能已经很强大,足以让你完成许多令人惊叹的工作,但它并不能满足所有需求。这也是Python社区中有许多善良且富有才华的人创建了大量Python库的原因。这些库提供了各种各样的功能和特性,可以扩展Python的功能,以帮助你更轻松地完成各种任务。
Python库(也称为Python包)是对Python基础库的扩展,提供了额外的功能,这些功能在Python基础库中并没有提供。例如,你可能会遇到一些以Microsoft Excel格式存储的数据,而你可能希望编写Python代码来读取这些Excel数据。在Python基础库中没有直接的方法可用来实现这一功能,但是一个名为pandas的库可以让你轻而易举地在Python中读取Excel数据。
如果你想要使用pandas库或其他Python库,需要先安装它们。虽然在安装Python的时候,会默认安装一些库,但大部分库需要手动安装。要手动安装Python库,你需要使用Python库安装工具pip。
如果你已经安装了Python,安装pip就非常简单。首先,你需要从https://bootstrap.pypa.io/下载一个Python脚本get-pip.py,这个脚本可以帮助你获取pip。下载脚本后,你需要运行这个脚本,具体运行方式取决于你使用的操作系统。
● 如果你使用的是Windows系统,需要打开命令提示符。你可以单击“开始”按钮并在搜索框中输入cmd,然后就可以看到命令提示符程序,单击它即可运行。
● 如果你使用的是macOS系统,需要打开终端。你可以打开Finder,再打开/Applications/ Utilities文件夹,最后双击Terminal。
● 如果你使用的是Linux系统,则需要打开终端。大多数Linux系统有一个默认的快捷方式来打开终端,例如在桌面上单击鼠标右键,然后在弹出的快捷菜单中选择并打开终端。
打开命令提示符(Windows)或终端(macOS或Linux)后,运行以下命令:
> python3 get-pip.py
这将在你的计算机上安装pip。如果在运行这个命令时出现错误,可能是因为get-pip.py文件存储在Python无法访问的位置。更明确地给出文件的位置通常会有帮助,因此你可以尝试执行如下命令:
> python3 C:/Users/AtticusFinch/Documents/get-pip.py
在这里,我们指定了一个文件路径(C:/Users/AtticusFinch/Documents/get-pip.py),它告诉Python可以查找get-pip.py文件的位置。你应该修改这个文件路径,让它与你的计算机上存储get-pip.py文件的位置匹配。例如,你可能需要将上面的AtticusFinch更改为自己的名字。
安装pip后,就可以用它来安装其他Python库了。例如,如果你想安装pandas库,可以使用以下命令:
> pip install pandas
你可以将pandas替换为要安装的任何其他Python库的名称。安装好pandas或其他Python库后,就可以在Python脚本中使用它们了。我们会在第1章详细介绍如何在Python中使用各种库。
前文详细介绍了如何安装Python以及如何手动安装Python库。你如果能完成这两件事,就能运行本书中的所有代码。
有些Python用户更喜欢使用其他工具来运行Python代码。例如,Anaconda是一种流行的工具,它允许你免费运行用于数据科学的Python代码。Anaconda包括Python,以及许多流行的库和其他功能。如果你想免费下载和使用Anaconda,可以访问https://www.anaconda.com/products/distribution。需要注意的是,Anaconda不是运行本书代码的必备工具。
Jupyter项目提供了一组流行的工具,这组工具可用于运行Python代码。你可以访问https://jupyter.org/了解其中最受欢迎的工具:JupyterLab和Jupyter Notebook。这些工具提供了高度可读、可交互、可共享且用户友好的环境,让用户能够轻松地运行Python代码。本书中所有的Python代码都是用Jupyter进行测试的,但你不需要使用Jupyter或Anaconda来运行本书中的代码,只需要Python和pip即可。使用Jupyter或Anaconda可能会更加方便,但这并非必需。
数据科学可以赋予你某些神奇的能力:预测未来的能力,提高利润的能力,自动收集大量数据的能力,将文字转化为数字的能力,等等。然而,要学会并熟练掌握这些能力并不容易,需要认真学习才能达到较高水平。学习数据科学虽难,但终将有所收获。如果你掌握了本书介绍的技能,就能在数据科学领域取得成功并享受其中的乐趣。本书介绍数据科学的主要思想及其在业务中的应用,它将帮助你在应用数据科学的道路上取得成功,并且让你成为你所在领域的数据科学专家。
本书提供如下资源:
● 本书图片文件;
● 本书思维导图;
● 异步社区7天VIP会员。
要获得以上资源,您可以扫描下方二维码,根据指引领取。
作者、译者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。
当您发现错误时,请登录异步社区(www.epubit.com),按书名搜索,进入本书页面,点击“发表勘误”,输入错误信息,点击“提交勘误”按钮即可(见下图)。本书的作者和编辑会对您提交的错误信息进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。
我们的联系邮箱是contact@epubit.com.cn。
如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。
如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们。
如果您所在的学校、培训机构或企业想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。
如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接通过邮件发送给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。
“异步社区”是由人民邮电出版社创办的IT专业图书社区,于2015年8月上线运营,致力于优质内容的出版和分享,为读者提供高品质的学习内容,为作译者提供专业的出版服务,实现作译者与读者在线交流互动,以及传统出版与数字出版的融合发展。
“异步图书”是异步社区策划出版的精品IT图书的品牌,依托于人民邮电出版社在计算机图书领域30余年的发展与积淀。异步图书面向IT行业以及各行业使用IT的用户。
本书是一本关于数据科学的书。因此,我们的学习之旅将从深入研究数据开始。在数据科学中,解决每个问题的第一步都是数据探索。通过仔细观察数据中的细节,你可以更好地理解数据,为下一步进行更复杂的分析提供更清晰的思路。此外,数据探索还有助于你尽早捕捉数据中的错误或其他问题。数据科学流程的第一步,是探索性数据分析。
本章首先介绍一个业务场景,并探讨如何使用数据优化业务运营。我们将演示如何使用Python读取数据,并检查数据的基本汇总统计指标。接着,介绍如何使用Python工具来创建数据图表。最后,我们将探讨如何基于数据分析改进业务实践。探索性数据分析是解决任何数据科学问题时都可以采取的第一步。我们开始吧!
假设你接到了来自华盛顿一家公司的CEO(Chief Executive Officer,首席执行官)工作邀请。该公司提供城市内的自行车短期租赁服务。尽管你没有经营共享单车公司的经验,但你仍然决定接受这份工作。
你第一天上班时就开始思考作为CEO的业务目标。你可能会考虑一些与客户满意度、员工士气、品牌认知、市场份额最大化、成本削减或收入增长等有关的目标。你如何决定首先追求哪些目标以及怎样实现这些目标呢?例如,考虑提高客户满意度。在专注于实现该目标之前,你需要了解客户对你公司的业务是否满意,如果不满意,需要找出导致客户不满意的原因以及改进的方法。或者,假设你更关心收入增长。在弄清楚如何增长收入之前,你需要先了解现在的收入是多少。换句话说,在更好地了解你的公司之前,你无法选择初步目标。
要了解你的公司,你需要数据。尽管通过总结公司数据的图表和报告可以了解一些信息,但只有深入研究数据才能获得更多的见解。因此,准备好自己的分析,可以帮助你更好地了解公司的运营状况和公司所面临的挑战。
让我们看一些真实的共享单车数据,假设这些数据来自你的公司。你可以从https://bradfordtuckfield.com/hour.csv下载这些数据。保存这些数据的文件的格式为.csv(稍后会详细讨论)。你可以使用电子表格编辑器(如Microsoft Excel或LibreOffice Calc)来打开这个文件并查看这些数据,如图1-1所示。
注意: |
共享单车数据的原始来源为Capital Bikeshare。这些数据由Hadi Fanaee-T和Joao Gama编译与维护,并由Mark Kaghazgarian在网上发布。 |
图1-1 在电子表格编辑器中查看共享单车数据
图1-1所示的数据集与其他数据集类似,是一个由行和列组成的矩形数组。它的每行代表从2011年1月1日0点0分到2012年12月31日晚上11点59分之间的某个特定时间点的信息。总共有超过17000个小时的数据。数据行按时间顺序排列,从2011年年初的数据开始,直至2012年年末的数据。简而言之,这是一个时间序列数据集。
该数据集的每一列都包含一个特定的指标,这些指标代表在测量时间段内的测量值。例如,windspeed这一列提供了华盛顿某个气象站每小时记录的风速测量值。需要注意的是,这里的单位与我们熟悉的英里/时(1英里约为1.609千米)不同。我们对测量值进行了标准化处理,使其始终在0和1之间。因此,我们只需要知道1表示风速快,0表示无风即可。
如果查看数据集的第2~6行(将标题作为数据表的第1行),你就会发现第2~6行的风速值都为0。这表明,在共享单车服务运行的最初的几个小时内,都是无风的。但是,在第7行,出现了风速的测量值0.0896,查看hr列,可以看到该测量值记录在hr = 5(即早上5点)时。第7行的dteday列值为2011-01-01,即这一行提供的是2011年1月1日的相关信息。
仅通过查看数据中的一些值,我们就可以讲出一个故事(尽管这个故事不是很有趣):“这是一个新年的清晨,外面刮着风。”如果想要了解有关这家共享单车公司及其业绩的故事,而不仅是天气,我们需要查看其他更多相关的列。
最重要的信息保存在数据集的最后3列:casual、registered和count。这些列表示每小时使用共享单车的人数。在你公司提供的服务中,注册用户可以享受折扣和其他福利,他们的自行车使用记录保存在registered列中。但是,人们也可以不注册就使用你公司的自行车,非注册用户的自行车使用记录保存在casual列中。每小时使用自行车的总用户数是casual和registered两列数值的总和,保存在count列中。
现在,你已经熟悉了数据集中与公司业务更相关的列,只需要查看它们中的数据就可以了解很多信息。例如,观察图1-1中的前20个小时,你会发现在大多数时间里,使用自行车的注册用户比非注册用户多(registered列的值比casual列的值大)。这只是一个简单的数字事实,但作为CEO,你应该仔细考虑它对你公司业务的影响。注册用户比非注册用户多可能意味着你公司在说服人们注册方面做得很好,但这也可能意味着没有注册就可以简单地使用你公司的服务并没有那么容易。你必须考虑哪一部分客户对你的目标更重要:是常规的注册用户,比如每日通勤者,还是偶尔的、不频繁使用共享单车的用户,比如观光游客。
我们可以更深入地研究非注册用户和注册用户的日常行为模式,以了解他们的更多信息。再次查看图1-1所示的小时数据,我们发现,在第一天的下午之前,非注册用户数量较少,但在下午1点前后达到峰值。即使在第一天凌晨1点,注册用户也相对较多,并在下午2点达到峰值。虽然注册用户和非注册用户之间的行为差异很小,但这些差异可能具有重要的意义。例如,它们可以反映出这些用户群体之间的人口统计差异。因此,可能需要针对不同的群体采用不同的营销策略。
想一想我们做了什么:仅通过查看前23行的几列数据,我们就已经了解了公司的一些重要信息,并有了一些商业思路。虽然数据科学以需要复杂的数学和计算机科学的“神秘”知识而闻名,但只要看一眼数据集,稍加思考,并应用一些常识,你也可以在某种程度上解决一些业务问题。
让我们更加仔细地研究数据。如果你在电子表格编辑器中打开数据文件(hour.csv),它会像图1-1所示一样呈现。不过,你也可以在文本编辑器(比如Windows下的Notepad++、macOS下的TextEdit,或者Linux下的GNU Emacs或gedit)中打开这个文件。在文本编辑器中打开这个文件时,数据如图1-2所示。
图1-2 文本格式的共享单车数据
图1-2所示的hour.csv文件中的原始数据没有像电子表格中那样对齐的列。请注意,文件中有很多逗号。该文件的扩展名为.csv,而csv是comma-separated values(逗号分隔值)的缩写,因为文件的每行中的数值之间都用逗号进行分隔。
当你使用电子表格编辑器打开一个.csv文件时,编辑器会试图将每个逗号解释为电子表格单元格之间的边界,以便能够以规整的、对齐的行和列形式显示数据。但实际上,数据本身并不是以电子表格的形式存储的:它只是一堆包含值的原始文本,每个值之间都用逗号分隔。
.csv格式文件简单且易于创建,并可以被许多程序打开,也容易进行修改。这就是数据科学家通常选择以.csv格式存储数据的原因。
使用Python可以进行比使用文本编辑器和电子表格编辑器更复杂的分析,并且可以实现自动化流程和加速分析。我们可以轻松地使用Python打开.csv文件。下面的3行Python代码用于读取hour.csv文件,并显示它的前5行数据:
import pandas as pd hour=pd.read_csv('hour.csv') print(hour.head())
稍后我们将更详细地查看此代码片段的输出结果。现在来看一下代码中的内容。此代码片段的目的是读取并显示数据。代码的第二行使用read_csv()方法读取数据。方法是一段执行单一、明确定义功能的代码单元。正如其名称所示,read_csv()用于读取存储在.csv文件中的数据。运行这行代码后,hour变量将包含hour.csv文件中的所有数据;接下来你可以在Python中访问这些数据。
在第三行,我们使用print()函数将数据显示(输出)在屏幕上。可以将第三行更改为print(hour),以查看整个数据集。但是数据集可能非常大,难以一次性全部阅读。因此,我们添加了head()方法,它仅返回数据集的前5行。
read_csv()和head()方法对我们来说都非常有帮助,但它们不是Python标准库(默认安装的标准Python功能)的一部分,而是第三方代码包(代码库)的一部分。第三方代码包可以通过Python脚本选择性地安装和使用。
这两种方法是一个名为pandas的流行包的一部分,pandas中包含处理数据所需的代码。前面代码片段的第一行为import pandas as pd,这行代码导入了pandas包,以便我们可以在Python中使用它。我们是通过别名pd引用pandas包的,因此每次想要访问pandas的函数或方法时,都可以使用pd而不必使用完整的pandas名称。当我们使用pd.read_csv()时,其实是访问pandas包中的read_csv()方法。
如果你在运行import pandas as pd时遇到错误,可能是因为pandas没有安装在你的计算机上(在导入包之前需要先安装它们)。要安装pandas或其他Python包,应该使用标准的Python包安装程序pip。你可以在本书前言中找到如何安装pip并使用它来安装像pandas这样的Python包的说明。在本书中,每次导入包时,你应该确保已经使用pip将需要导入的包安装到了你的计算机上。
运行前面的代码片段时,你可能会遇到另外的错误。最常见的错误之一是Python无法找到hour.csv文件。如果发生这种情况,Python将输出错误报告。错误报告的最后一行可能显示如下内容:
FileNotFoundError: [Errno 2] No such file or directory: 'hour.csv'
即使你不是Python专家,也可以推断出以上内容意味着什么:Python尝试读取hour.csv文件,但无法找到它。这是一个令人沮丧的错误,但很好解决。首先,确保你已经下载了hour.csv文件,并确保它在你计算机上的名字也是hour.csv(因为计算机上的文件名必须与Python代码中的文件名完全匹配)。
如果Python代码中的hour.csv文件名(全部为小写字母)拼写正确,那么问题可能出在文件路径上。请记住,计算机上的每个文件都有一个唯一的文件路径,该路径准确地指定了你需要导航到的位置。文件路径可能如下所示:
C:\Users\DonQuixote\Documents\hour.csv
这个文件路径采用的是Windows操作系统中使用的格式。如果你使用的是Windows系统,请确保你的目录和文件名没有使用任何特殊字符(如非英文字母字符),因为带有特殊字符的文件路径可能导致错误。以下是另一种表示文件路径的方法,它采用的是类UNIX操作系统(包括macOS和Linux)中使用的格式:
/home/DonQuixote/Documents/hour.csv
你会发现Windows文件路径与macOS和Linux文件路径看起来不同。在macOS和Linux中,我们只使用正斜线(/),并将正斜线作为路径的开始,而不使用像C:\这样的驱动器名称。当你将文件读入Python时,避免发生无法找到文件这种错误的最简单的方法是指定完整的文件路径,如下所示:
import pandas as pd hour=pd.read_csv('/home/DonQuixote/Documents/hour.csv') print(hour.head())
当你运行这个代码片段时,你可以将read_csv()方法中的文件路径替换为自己的计算机上的文件路径。当你运行之前的代码片段,并正确指定与你的计算机上的hour.csv文件位置匹配的文件路径时,你应该得到以下输出:
instant dteday season yr ... windspeed casual registered count 0 1 2011-01-01 1 0 ... 0.0 3 13 16 1 2 2011-01-01 1 0 ... 0.0 8 32 40 2 3 2011-01-01 1 0 ... 0.0 5 27 32 3 4 2011-01-01 1 0 ... 0.0 3 10 13 4 5 2011-01-01 1 0 ... 0.0 0 1 1 [5 rows x 17 columns]
这个输出显示了hour.csv文件数据的前5行。这些数据按列排列,看起来类似于该文件的电子表格输出。就像在图1-1中一样,每一行都包含与共享单车公司特定历史时间(精确到小时)相关的数据。
在这里,我们用省略号代替了某些列,这样在屏幕上更容易阅读数据,也很容易将它们复制并粘贴到文本文档中(你可能会看到所有的列而没有省略号——具体显示取决于你如何在计算机上对Python和pandas进行配置)。就像在电子表格编辑器中打开文件时所做的那样,我们可以查看其中的数据,以发现公司的历史运行情况,并获得改善业务运营的想法。
除了查看数据,量化数据的重要属性也很重要。我们可以从计算其中一列的均值开始量化,如下所示:
print(hour['count'].mean())
在这里,我们通过使用方括号([])和列名(count)来访问hour数据集的count列。如果单独运行print(hour['count']),你会看到整列的内容。但是我们只需要该列的均值,而不需要该列中的每个具体值,所以我们添加了pandas提供的mean()方法。我们看到均值约为189.46。从商业的角度来看,均值是对数据涵盖的两年的业务规模的粗略衡量。
除了计算均值,我们还可以计算其他重要指标,如下所示:
print(hour['count'].median()) print(hour['count'].std()) print(hour['registered'].min()) print(hour['registered'].max())
在这里,我们使用median()方法计算count列的中位数,使用std()方法计算count列的标准差(标准差是对一组数字离散程度的度量,它有助于我们了解数据中每个小时用户数量的变化量)。我们还分别使用min()和max()方法计算registered列的最小值和最大值。注册用户的数量从0到886不等,这显示了每小时的记录,以及如果你想让你的业务比以前更好,需要打破的纪录。
以上简单的计算被称为汇总统计,对数据集使用这些计算将很有帮助。检查数据集的汇总统计可以帮助你更好地了解数据,也可以帮助你更好地了解业务。
这些汇总统计看起来很简单,但其实许多 CEO 都无法马上回答有关他们公司的数据统计问题。了解像每天各时段的平均用户数量这样的简单数据,可以帮助了解公司的规模,以及公司有多少成长的空间。
这些汇总统计还可以与其他信息结合使用,以使我们了解更多内容。例如,如果你查看公司每小时自行车使用价格,将其乘以count列的均值,则可以获得两年内的总收入(我们使用的数据集的时间跨度为两年)。
你可以使用pandas中的方法(如mean()和median())手动获取汇总统计,就像之前所做的那样;也可以使用另一种方法,一次性获取多个汇总统计信息,如下所示:
print(hour.describe())
在这里,我们使用describe()方法获取数据集中所有变量的汇总统计。输出如下所示:
instant season ... registered count count 17379.0000 17379.000000 ... 17379.000000 17379.000000 mean 8690.0000 2.501640 ... 153.786869 189.463088 std 5017.0295 1.106918 ... 151.357286 181.387599 min 1.0000 1.000000 ... 0.000000 1.000000 25% 4345.5000 2.000000 ... 34.000000 40.000000 50% 8690.0000 3.000000 ... 115.000000 142.000000 75% 13034.5000 3.000000 ... 220.000000 281.000000 max 17379.0000 4.000000 ... 886.000000 977.000000 [8 rows x 16 columns]
可以看到,describe()方法为我们提供了一个完整的表格,该表格包含几个常用的指标,比如每个变量的均值、最小值和最大值等。describe()的输出还包含百分位数,例如,25%表示数据集内各个数值类型列的第25百分位数。我们可以看到count列的第25百分位数为40,这意味着数据集内有25%的“每小时用户数”为40或40以下,而有75%的“每小时用户数”超过40。
使用describe()方法得到的表格很有用,它可以帮助我们检查数据问题。数据集通常会包含一些明显的错误,这些错误可以从describe()的输出中发现。例如,如果你在一个人员数据集上运行describe()方法,发现这些人的平均年龄是200岁,那么数据集中肯定存在错误。这样的错误显而易见,但在一篇发表于知名学术期刊上的研究论文中,确实发现了这种错误(平均年龄大于200岁)——如果那些研究人员使用了describe()就好了!你应该查看每个数据集的describe()输出,确保所有的值至少是在合理的范围内的。如果你发现了看起来不可信的数据,就需要找出数据中的问题并对问题进行修正。
截至目前,我们已经可以利用从数据中学到的知识来提出改进业务的方法。例如,我们发现,在数据记录的前24小时内,夜间骑行人数远远少于白天骑行人数。我们还发现,每小时用户数存在很大的变化:25%的时间内骑行人数不超过40,但在某个小时内骑行人数为977。作为CEO,你可能希望更多的时间中每小时的骑行人数接近977,而更少的时间中每小时的骑行人数不超过40。
你可以通过多种方式实现这个目标。例如,你可以降低夜间用车价格,以吸引更多的客户,并由此减少骑行人数较少的时间。仅通过简单的探索,你就可以继续从数据中学习并得到改进业务的方法。
前文我们检查了与完整数据集相关的汇总统计数据,然后考虑在夜间提供更低的用车价格以增加夜间骑行人数。要实现这个想法,我们应该检查夜间的相关统计数据。
我们可以从使用loc()方法开始:
print(hour.loc[3,'count'])
loc()方法允许我们指定数据的子集。使用loc()时,需要使用方括号和格式“[<行>, <列>]”来指定要选择的子集。在这里,我们指定了[3, 'count'],表示选择数据的第3行和count列。我们从这里得到的输出是13,如果你查看图1-1或图1-2中的数据,会发现这个结果是正确的。
这里需要指出的一件重要的事情是,在Python和pandas中,标准做法是使用从0开始的索引。我们从0开始计数,所以如果数据有4行,我们会将这些行标记为0、1、2、3。数据的第4行对应的索引为3。类似地,数据的第3行的索引为2,第2行的索引为1,第1行的索引为0。这就是为什么当我们运行print(hour.loc[3, 'count'])时,得到的是13(它是存储在count列的第4个值,来自索引为3的行),而不是32(它是存储在count列的第3个值,来自索引为2的行)。许多人对使用从0开始的索引并不熟悉,但通过积累经验,你可以逐渐适应这种使用方式。
在之前的代码片段中,我们查看了一个由单个数字(单行和单列的count)组成的子集。但你可能想了解由多个行或多个列组成的子集。通过使用冒号(:),我们可以指定想要查看的行范围:
print(hour.loc[2:4,'registered'])
在这个代码片段中,我们指定了想要获取的registered列的值。在方括号中指定2:4,表示我们想要获取从第3行到第5行的所有行,因此输出结果为3个数字:27、10和1。查看这些行,你会发现这些是凌晨2点、3点和4点的相关数据。我们并没有输出所有的数据,而是只输出了3行数据。由于只输出了一个子集,可以称这种方法为子集选择——选择数据的子集。在探索和分析数据时,这种方法非常有效。
除了一次性查看几个相邻的行,我们还可以查看数据中的所有夜间观察结果。我们可以使用loc()方法结合逻辑条件来实现:
print(hour.loc[hour['hr']<5,'registered'].mean())
这个代码片段使用loc()方法来访问数据的子集,就像之前所做的一样。然而,它没有指定特定的行号,而是指定了一个逻辑条件hour['hr']<5,该条件意味着它将选择数据中hr变量值小于5的每一行,我们将得到凌晨0点到4点对应的数据子集。我们可以为更复杂的逻辑指定多个条件,例如,可以专门检查在寒冷的清晨和温暖的清晨的平均骑行人数:
print(hour.loc[(hour['hr']<5) & (hour['temp']<0.50),'count'].mean()) print(hour.loc[(hour['hr']<5) & (hour['temp']>0.50),'count'].mean())
在这里,我们指定了多个逻辑条件,用“&”字符来连接,表示“与”,这意味着两个条件必须同时成立。第一行选择了hr值小于5且temp值小于0.50的行。在hour数据集内,temp变量记录的是温度,但没有使用我们熟悉的华氏温度或摄氏温度,而是使用一种特殊的度量标准,将所有温度值转化为0~1,其中0表示较低的温度,1表示较高的温度。无论何时处理数据,确保准确知道每个变量使用的单位非常重要。我们通过指定hour['temp']<0.50选择较低温度的时间段,通过指定hour['temp']>0.50选择较高温度的时间段。将这些时间段的记录放在一起,就能够比较寒冷的清晨和温暖的清晨的平均骑行人数了。
我们还可以使用“|”符号表示“或”,如下面的例子所示:
print(hour.loc[(hour['temp']>0.5) | (hour['hum']>0.5),'count'].mean())
这行代码选择了温度较高或湿度较高的行中的平均骑行人数,并非要求两个条件同时满足。通过获取上面的数据,可以知道在天气状况不好的情况下各个时间段内的骑行人数。
夜间折扣并不是增加骑行人数和收入的唯一可行策略。你还可以考虑在特定季节或一年中的某些时间段提供特别优惠。在数据中,season变量使用1表示冬季,2表示春季,3表示夏季,4表示秋季。我们可以使用groupby()方法来获取每个季节每小时的平均骑行人数:
print(hour.groupby(['season'])['count'].mean())
这个代码片段中的很多内容,我们应该已经熟悉了。我们使用print()来查看与hour数据相关的指标,使用mean()方法获取均值,使用['count']来访问数据的count列。因此,这个代码片段的结果将显示按照季节进行分组之后的各个小时对应的平均骑行人数。
唯一的新部分是groupby(['season'])。这是一个将数据分组的方法,在本例中,每个出现在season列中的唯一值对应一个组。如下输出结果显示了每个季节的平均骑行人数:
season 1 111.114569 2 208.344069 3 236.016237 4 198.868856 Name: count, dtype: float64
解读这个输出结果很简单:在第一个季节(冬季),每小时的平均骑行人数约为111;在第二个季节(春季),每小时的平均骑行人数约为208;依此类推。结果明显存在季节性模式:春季和夏季的骑行人数较多,秋季和冬季的骑行人数较少。groupby()方法也可以用于在多个列上进行分组操作,如下所示:
print(hour.groupby(['season','holiday'])['count'].mean())
运行结果如下所示:
season holiday 1 0 112.685875 1 72.042683 2 0 208.428472 1 204.552083 3 0 235.976818 1 237.822917 4 0 199.965998 1 167.722222 Name: count, dtype: float64
在这里,我们指定了两个列进行分组:季节(season)和假期(holiday)。这将数据分成了4个季节,并将每个季节分成假期(用1表示)和非假期(用0表示),分别显示每个季节的假期和非假期的平均骑行人数。这样我们可以按照季节了解假期和非假期骑行人数的差异。在较寒冷的季节里,假期的骑行人数明显少于非假期的,而在较温暖的季节里,假期的骑行人数与非假期的大致相等。了解这些差异可以帮助你做出有关业务运营的决策,并可能为你在不同季节或不同假期制定策略提供灵感。
hour数据集很大,可以以许多不同的方式进行研究。我们查看了一些子集并得到了一些想法。你应该做更多的工作:检查与所有列相关的子集,并从多个角度探索数据。即使不进行高级统计和机器学习,你也可以了解很多内容,并获得许多有用的想法。
汇总统计数据对于数据探索很有帮助。然而,关于探索性数据分析的一个非常重要的部分,我们还没有介绍,即绘图或对数据进行可视化。
在进行数据分析时,应该尽早且经常地绘制数据图表。下面我们将使用一个流行的绘图包——Matplotlib。我们可以按照以下方式绘制数据的简单图表:
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 6)) ax.scatter(x = hour['instant'], y = hour['count']) plt.show()
在这里,我们导入了Matplotlib包,并给它起了一个别名plt。接下来,我们创建了一个名为fig的图形和一个名为ax的坐标轴。图形fig将包含我们绘制的所有图表或一组图表的相关信息。坐标轴ax将为我们提供实际绘制图表所需的有用方法。subplots()方法为我们创建了这两个对象,我们可以在该方法内指定图形的尺寸(通过figsize进行指定)。在本例中,我们指定了一个尺寸为(10, 6)的图形,这意味着图形的宽度为10in(1in=2.54cm),高度为6in。
接下来,我们使用scatter()方法绘制图表。在scatter()方法中,我们指定x=hour['instant'],这样x轴将显示hour数据中的instant变量;指定y=hour['count'],这样y轴将显示hour数据中的count变量。最后,我们使用plt.show()将该图表显示在屏幕上。这段代码创建的图表如图1-3所示。
图1-3 两年内每小时对应的骑行人数
在这个图表中,每个点都代表数据集中记录的一个小时对应的骑行人数。第一个小时(2011年的开始)对应的点位于图表最左侧,最后一个小时(2012年的结束)对应的点位于图表最右侧,而其他时间(小时)对应的点依次排列在图表中间。
这个图表被称为散点图,是我们常用的图表之一,它显示了数据中的每个观测值,使我们可以很容易地对数据的关系进行观察。之前的groupby()已经给出了一个关于季节变化的提示,现在我们可以看到一个完整的季节变化的表示。我们还可以看到随着时间的推移,骑行人数的总体增长情况。
虽然图1-3对数据进行了展示,但是它的展示不够明晰。我们可以按照以下方式为图表添加标题和标签:
fig, ax = plt.subplots(figsize=(10, 6)) ax.scatter(x = hour['instant'], y = hour['count']) plt.xlabel("Hour") plt.ylabel("Count") plt.title("Ridership Count by Hour") plt.show()
这个代码片段使用xlabel()为x轴添加标签,使用ylabel()为y轴添加标签,并使用title()为图表添加标题。你可以在这些方法中根据需要设定文本。输出结果如图1-4所示。
图1-4 带有坐标轴标签和标题的每小时骑行人数图表
hour数据集非常大,一次性查看所有数据非常困难。我们看看如何绘制较小的数据子集图表。
我们可以将之前创建的数据子集作为数据源来绘制图表:
hour_first48=hour.loc[0:48,:] fig, ax = plt.subplots(figsize=(10, 6)) ax.scatter(x = hour_first48['instant'], y = hour_first48['count']) plt.xlabel("Hour") plt.ylabel("Count") plt.title("Count by Hour - First Two Days") plt.show()
在这里,我们定义了一个名为hour_first48的新变量。这个变量包含与原始数据的第1行到第49行相关的数据,大致对应于数据中前两天的完整数据。
请注意,我们通过hour.loc[0:48,:]来获得这个子集。这与我们之前使用的loc()方法相同。我们使用0:48来指定索引在0~48的行,但我们没有指定任何列——只是在通常指定列名的位置写了一个冒号(:)。这是一个有用的快捷方式:单独放置的冒号使pandas了解我们想要选择数据集的所有列,因此我们不需要逐个写出每个列的名称。通过这个数据子集绘制的图表如图1-5所示。
图1-5 数据集内前两天的每小时骑行人数统计
通过仅绘制两天而不是绘制两年的数据,避免了绘图点重叠和覆盖的问题。我们可以更清楚地看到每个观测结果。当你有一个大型数据集时,对数据集进行分别绘制是不错的选择:一次性绘制整个数据集(以了解总体情况),以及绘制数据集的较小子集(以了解个别观测情况和小规模数据分布情况)。在上述例子中,除了年度的长期季节性规律,我们还可以看到数据在一天当中的规律。
有很多方法可以改变图形的外观。我们可以通过调整scatter()方法中的参数来获得不同的图形外观:
fig, ax = plt.subplots(figsize=(10, 6)) ax.scatter(x = hour_first48['instant'], y = hour_first48['count'],c='red',marker='+') plt.xlabel("Hour") plt.ylabel("Count") plt.title("Count by Hour - First Two Days") plt.show()
在这里,我们使用c参数来指定绘图点的颜色(red)。我们还指定了一个marker参数来改变标记的样式,即绘图点的形状。通过将marker参数指定为'+',我们得到的绘图点看起来像是小加号而不是小圆点。图1-6显示了新的输出结果。
因为本书采用单色印刷,所以在图1-6中无法看到红色的绘图点。但如果你运行上面的代码,你将在屏幕上看到红色的绘图点。
图1-6 使用不同的图形外观显示骑行人数
散点图并不是绘图的唯一选择,我们还可以尝试绘制折线图:
fig, ax = plt.subplots(figsize=(10, 6)) ax.plot(hour_first48['instant'], hour_first48['casual'],c='red',label='casual',linestyle='-') ax.plot(hour_first48['instant'],\ hour_first48['registered'],c='blue',label='registered',linestyle='--') ax.legend() plt.show()
在这个例子中,我们使用ax.plot()而不是ax.scatter()来绘制图表。ax.plot()方法允许我们绘制折线图。在这里,我们调用ax.plot()两次来在同一个图表上绘制两条线,这可以让我们对注册用户和非注册用户在各时段的数量进行比较,结果如图1-7所示。
图1-7 通过折线图显示前两天的数据中注册用户和非注册用户在各时段的数量
图1-7所示的图表显示,非注册用户的数量几乎始终低于注册用户的数量。图例指示了对注册用户和非注册用户使用了不同颜色和样式(非注册用户为红色实线,注册用户为蓝色虚线)的线条。请在你自己的环境中运行此代码,以便更清楚地看到线条的颜色及其对比效果。
我们还可以尝试使用另一种绘图方式:
import seaborn as sns fig, ax = plt.subplots(figsize=(10, 6)) sns.boxplot(x='hr', y='registered', data=hour) plt.xlabel("Hour") plt.ylabel("Count") plt.title("Counts by Hour") plt.show()
这次,我们导入了一个名为seaborn的包。这个包是基于Matplotlib开发的,所以它包含Matplotlib的所有功能,还增加了很多功能,可以帮助我们快速创建美观且信息丰富的图表。我们使用seaborn的boxplot()方法来创建一种新的图表类型:箱线图。图1-8展示了这段代码创建的箱线图。
图1-8 显示一天中按小时分组的骑行人数的箱线图
你可以看到24个垂直的箱线图平行绘制在一起,每一个箱线图代表一天中特定小时的信息。箱线图是一种简单的图表类型,但它提供了大量的信息。在箱线图中,每个矩形的上下水平边界分别代表所绘制数据的第75百分位数和第25百分位数。矩形内部的水平线代表中位数(或第50百分位数)。从每个矩形的顶部和底部延伸出的垂直线代表未被视为异常值的所有观测值的范围。超出垂直线范围的单独绘制点(观测值)被视为异常值。
在图1-8中查看所有箱线图,你能够比较不同时间的骑行人数。例如,第6个小时(大约早上5点)的骑行人数的中位数非常低,但第7个小时(大约早上6点)的骑行人数的中位数要高得多,第8个小时(大约早上7点)的骑行人数的中位数更高。在第17个小时和第18个小时(下午4点和5点左右),骑行人数再次增加。这些高峰也许表示你的许多客户使用你们公司提供的自行车上下班。
正如你所期望的,我们可以绘制更多类型的图表。一种有用的图表类型是直方图,它可以通过以下方式创建:
fig, ax = plt.subplots(figsize=(10, 6)) ax.hist(hour['count'],bins=80) plt.xlabel("Ridership") plt.ylabel("Frequency") plt.title("Ridership Histogram") plt.show()
这段代码使用hist()方法绘制直方图,结果如图1-9所示。
图1-9 显示每种骑行人数频数的直方图
在直方图中,每个条形的高度代表频数。在这个例子中,直方图显示了每种骑行人数的频数。例如,在x轴大约800的位置,你会看到高度接近0的条形。这意味着数据集内只有极少数时间的骑行人数接近800。相比之下,在x轴大约200的位置,你会看到较高的条形,其高度接近500。这表明在我们的数据中,有接近500个小时的骑行人数接近200。我们在这个直方图中看到的模式对于企业来说是常见的:很多的时间内有很少的客户,很少的时间内有很多的客户。
你可以使用直方图来评估你们公司的业务。例如,你们公司今天有1000辆自行车可供出租。你认为通过出售其中的200辆自行车可以节省一些费用,这样,你将获得额外的流动资金,而且不必担心多余自行车的维护和存放。这将使你剩下800辆可供出租的自行车。通过查看直方图,你可以准确地了解这种变化对公司的影响有多大:因为只有很少一部分时间对自行车的需求超过800辆,所以出售200辆自行车对公司的业务不会造成很大的影响。你可以查看直方图,以决定你可以安心出售多少辆自行车。
另一种类型的图表是配对图(pair plot),它为数据中的每一对可能组合的变量绘制散点图:
thevariables=['hr','temp','windspeed'] hour_first100=hour.loc[0:100,thevariables] sns.pairplot(hour_first100, corner=True) plt.show()
在这里,我们创建了一个变量thevariables,它是我们将绘制的3个变量的列表(由于本书的篇幅有限,我们只绘制3个变量而没有绘制所有变量)。我们还创建了hour_first100变量,它是我们的完整数据集的一个子集,仅包含hour数据集的前100条记录。同样,seaborn包通过提供pairplot()方法来帮助我们创建图表。代码运行结果如图1-10所示,它是一个包含散点图和直方图的图表集合。
图1-10 配对图显示了所选变量之间的关系
图1-10所示的配对图显示了我们选择的数据子集内变量之间的所有可能组合的散点图,以及我们选择的各个变量的直方图。这里绘制了大量的数据,但散点图并没有显示出变量之间的明显关系——这些关系似乎是随机的。
有时候,当我们绘制配对图时,我们看到的不一定是随机情况,我们也可以看到变量之间的明显关系。例如,如果数据中有降雪量的测量值,我们会发现随着温度的升高,降雪量下降,反之亦然。这种变量之间的明显关系称为相关性,我们将在下一节中探索相关性。
如果一个变量的变化总是与另一个变量的变化同时发生,那么这两个变量之间存在相关性。如果两个变量正相关,意味着它们的变化是一致的:一个变量增加时,另一个变量也趋向增加;一个变量减小时,另一个变量也趋向减小。我们可以在世界上找到无数个正相关的例子。例如,一个城市的猫粮购买量与该城市的家养猫数量呈正相关。在正相关的两个变量中,如果一个变量很高,另一个变量往往也很高;如果一个变量很低,另一个变量往往也很低。
我们还可以讨论负相关:如果两个变量中的一个在另一个变量减小时趋向增加,或者在另一个变量增加时趋向减小,那么这两个变量负相关。负相关的例子在世界上也很常见。例如,一个城市的常住居民每年花费在厚冬衣上的平均金额与该城市的平均气温呈负相关。在负相关的两个变量中,一个变量较高,而另一个变量往往较低,反之亦然。
在数据科学领域,找到并理解相关性(无论是正相关还是负相关)非常重要。如果你能找到并理解相关性,作为CEO,你的业绩将会有显著提升。例如,你可能发现骑行人数与温度呈正相关。这意味着在温度较低时,骑行人数往往较少。你甚至可以考虑在骑行人数较少的季节出售一些自行车,以增加现金流,而不是让很多自行车闲置。当然,你所选择的具体做法将取决于具体情况的许多其他细节,深入理解数据将帮助你做出明智的商业决策。
我们可以在Python中计算相关系数:
print(hour['casual'].corr(hour['registered'])) print(hour['temp'].corr(hour['hum']))
在这里,我们使用了pandas提供的一个功能,即corr()方法。corr()方法用于计算相关系数。我们可以计算许多类型的相关系数,但默认情况下,corr()计算的是皮尔逊相关系数。这是最常用的相关系数,本书中提到的相关系数指的都是皮尔逊相关系数。
皮尔逊相关系数是一个介于−1~1的数字,通常用变量r表示。它用于描述两个变量之间的关系:其符号(正号或负号)描述了相关性的类型,而其数值描述了相关性的强度。如果r是一个正数,表示两个变量呈正相关;如果r是一个负数,表示两个变量呈负相关;如果r为0或者非常接近0,我们称这些变量之间没有相关性。
以上代码片段的第一行计算了数据中casual和registered变量之间的相关系数。对于这两个变量,r约为0.51,这是一个正数,表示正相关关系。
除了注意相关系数是正数、负数还是0,我们还关注它的确切数值,也就是它的大小。如果相关系数的绝对值很大(相关系数远离0,接近1或−1),我们通常说两个变量之间的相关性很强。请参考图1-11所示的相关性例子。
图1-11 正相关变量
在图1-11中,你可以看到两幅图。第一幅图显示了华氏温度和摄氏温度之间的关系。你可以看到华氏温度和摄氏温度呈正相关:当一个温度上升时,另一个温度也上升,反之亦然。第二幅图显示了你们公司中注册用户骑行人数和非注册用户骑行人数之间的关系。同样,我们看到了正相关性:非注册用户骑行人数增加时,注册用户骑行人数也趋向增加,反之亦然。
图1-11所示的这两个相关性都是正相关,但我们可以看到它们之间存在定性的差异。华氏温度和摄氏温度之间的关系是确定性的:知道摄氏温度可以准确地知道华氏温度,没有不确定性或猜测。这种在图上呈直线的确定性正相关也被称为完全正相关,当我们为完全正相关测量相关系数时,会发现r = 1。
相比之下,注册用户骑行人数和非注册用户骑行人数之间的关系不是确定性的。通常情况下,非注册用户骑行人数较多对应着注册用户骑行人数较多。但有时候并不是这样;我们不能通过一个变量来完美预测另一个变量。当两个变量相关但没有确定性关系时,我们说这两个变量之间的关系具有“噪声”或随机性。
随机性很难精确定义,但你可以将其理解为不可预测性。当你知道一个摄氏温度时,你可以完全准确地预测华氏温度。相比之下,当你知道非注册用户骑行人数时,你可以预测注册用户骑行人数,但你的预测可能不完全准确。当存在这种不可预测性时,两个变量的相关系数将小于1。在这种情况下,我们可以计算注册用户骑行人数和非注册用户骑行人数之间的相关系数,得到r = 0.51。
你可以将相关系数的大小视为衡量两个变量之间关系中随机性大小的指标。较大的相关系数对应较小的随机性(更接近确定性关系,如华氏温度和摄氏温度之间的关系)。较小的相关系数对应较大的随机性和较小的可预测性。相关系数0表示变量之间没有任何关系,可将其视为纯随机性或纯噪声的表现。
在图1-12中,你可以观察到一些不同程度的负相关性的例子。
图1-12 负相关变量
在图1-12中,我们看到了与图1-11相似的概念。第一幅图显示了完全负相关性:体积和压力之间的确定性关系。这里的相关性正好对应r = −1,表示变量之间不存在任何随机性;每个变量都可以通过使用另一个变量来完全预测。
第二幅图显示了hour数据集中温度和湿度之间的关系。这两个变量也具有负相关性,但相关系数要小得多:r约为−0.07。就像我们对图1-11中的正相关性所给出的解释一样,我们可以将这些相关系数解释为衡量随机性的指标:绝对值较大的相关系数(即接近1或−1)表示相关性较大且随机性较小,而绝对值较小的相关系数(接近0)表示具有较大的随机性。这里r = −0.07时,我们将其解释为温度和湿度呈负相关,但它们之间的相关性非常弱,接近纯随机性。
在观察相关性时,有一点非常重要:相关并不意味着因果关系。当我们观察到强相关性时,我们唯一能确定的是两个变量倾向于同时变化,我们无法确定一个变量发生变化是否导致另一个变量发生变化。
例如,假设我们研究硅谷的初创公司,发现它们的月度收入与购买的乒乓球桌数量相关。我们可能会匆忙地通过这种相关性得出结论,即乒乓球桌的购买将导致收入增加;也许打乒乓球可帮助员工放松和促进员工间的友情,进而提高了生产效率,或者打乒乓球营造的有趣氛围能帮助公司更好地留住员工并招聘到更多新员工。
然而,这些结论可能完全错误,也许因果关系是相反的:成功的公司(与乒乓球桌完全无关)拥有更高的收入,而由于它们的预算突然增加,它们使用一部分新增的资金购买娱乐设施,比如购买乒乓球桌。在这种情况下,收入增加将导致购买乒乓球桌,而不是购买乒乓球桌导致了收入增加。
最后,相关性可能只是巧合。也许购买乒乓球桌并不会导致收入增加,收入增加也不会导致购买更多的乒乓球桌,而是我们观察到了一种偶然的相关性:只是由于巧合产生的相关性,并不表示任何因果关系或特殊关系。相关性也可能是由被忽略的某个变量(即我们没有观察到的某个变量)造成的,但它独立地导致了收入增加和购买乒乓球桌同时发生。
无论如何,重要的是,在发现和解释相关性时要始终谨慎。
我们不仅可以计算两个变量之间的单个相关系数,还可以通过创建一个相关矩阵来进一步分析变量之间的相关性。相关矩阵是一个由数字组成的矩阵(或矩形数组),其中每个元素都是表示两个特定变量之间关系的相关系数。相关矩阵将显示出所有变量之间的关系:
thenames=['hr','temp','windspeed'] cor_matrix = hour[thenames].corr() print(cor_matrix)
在这里,我们仍然使用之前提到的corr()方法。当我们不在圆括号内传入任何参数时,使用corr()方法会创建一个包含数据集中所有变量之间相关系数的相关矩阵。在上面的例子中,我们创建了一个较小的相关矩阵,显示3个选定变量之间的相关系数。我们计算得到的相关矩阵如下所示:
hr temp windspeed hr 1.000000 0.137603 0.137252 temp 0.137603 1.000000 −0.023125 windspeed 0.137252 −0.023125 1.000000
在这个3×3的矩阵中,每个元素都是一个相关系数。例如,在第二行第三列,你可以看到风速和温度之间的相关系数约为−0.023。严格来说,它表示负相关,但它非常接近0,我们通常会将这两个变量理解为不相关的两个变量。
你还可以看到矩阵中有3个相关系数等于1.0。这是符合预期的:这些完全正相关性用于衡量每个变量与自身的相关性(hr与hr的相关性,temp与temp的相关性,windspeed与windspeed的相关性)。每个变量与自身总是具有完全正相关性。创建相关矩阵是在数据中查找所有变量之间的关系,并找到正相关或负相关的快速且简便的方法。
在创建相关矩阵之后,我们可以绘制所有相关系数的图,使矩阵更易于理解:
plt.figure(figsize=(14,10)) corr = hour[thenames].corr() sns.heatmap(corr, annot=True,cmap='binary', fmt=".3f", xticklabels=thenames, yticklabels=thenames) plt.show()
在这里,我们创建了一个热力图。在这种类型的图表中,单元格的颜色深浅表示该单元格中数值的大小。图1-13中的热力图显示变量之间的相关性测量值。
图1-13 在热力图中显示相关性测量值
图1-13所示的热力图展示了9个矩形。正如其中右侧的图例所示,矩形的颜色越深,表示相关性越高;颜色越浅,表示相关性越低。通过相关矩阵的热力图,可以更快速地查看变量之间的相关关系,因为强相关性对应的颜色很容易引起注意。
如果你喜欢彩色图而不是灰度图,你可以在sns.heatmap()方法中更改cmap参数。cmap参数指的是热力图的颜色方案,通过选择不同的cmap值,你可以获得不同的颜色方案。例如,如果你使用cmap='coolwarm',你得到的热力图中,较高的值用红色调表示,较低的值用蓝色调表示。
热力图不仅可以用于显示相关矩阵,还可以用于显示其他变量。例如,我们可以绘制一个显示一周中每个小时骑行人数的热力图:
# 创建数据透视表 df_hm =hour.pivot_table(index = 'hr',columns ='weekday',values ='count') # 绘制热力图 plt.figure(figsize = (20,10)) # 调整图像尺寸 sns.heatmap(df_hm, fmt="d", cmap='binary',linewidths=.5, vmin = 0) plt.show()
为了创建这个图,我们需要创建一个数据透视表,即一个由分组值组成的表格。如果你在工作中经常使用电子表格编辑器,那么你应该使用过数据透视表。在这里,我们的数据透视表根据每周中的星期几和每天的小时将数据集内的值进行了分组。我们有每天(0~6,代表从周日到周六)每小时(0~23)的骑行人数。在创建了这样分组的数据透视表之后,我们可以使用heatmap()方法创建图1-14所示的热力图。
图1-14 每天每小时对应的骑行人数
在图1-14所示的热力图中,颜色较深的矩形表示在这个小时内骑行人数较多,而颜色较浅的矩形表示在这个小时内骑行人数较少。我们可以看到上午8点和下午5点左右出现了通勤高峰。我们还可以推断出在周六和周日下午更多的人选择骑自行车出行。
从商业角度来看,图1-14所示的热力图可以给我们提供许多商业创意。例如,在工作日上午8点左右骑车人数的激增,可能会给你一个增加收入的想法。就像我们设想的在用车较少的时间段提供折扣一样,我们也可以考虑相反的策略:在用车较多的时间段使用高峰定价(暂时提高价格)。Uber、Lyft和Grab等运输网络公司就采用了高峰定价策略,不仅可以增加收入,还可以确保产品的高可用性。
到目前为止,我们只查看了一个数据集,并且只进行了简单的探索与分析。随着你作为CEO的工作不断深入,你将需要对你的业务及公司运作方式做出许多决策。本章中的探索方法可以应用于你遇到的任何其他业务问题。例如,你可以考虑将自行车租赁与清爽的饮料销售捆绑在一起,从中获得额外的收入(更不用说为骑行者提供安全保险服务了)。分析与你的客户、他们的骑行习惯以及他们在骑行过程中的饮水量相关的数据,可以帮助你确定这个策略是否可行。
其他分析可能与你的自行车修理需求有关。你的自行车需要修理的频率为多少?修理费用是多少?你可以查看修理的时间,确保修理不是在高峰时段进行的。可以检查不同类型自行车的修理费用。你可以查看修理费用的直方图,并检查是否有任何异常值使公司的成本过度增加。这些探索将帮助你更好地了解你的业务,并为改进经营方式提供思路。
到目前为止,我们的分析并不是非常复杂,主要计算了一些汇总统计量并绘制了图表。但是这些简单的计算和图表,结合常识,可以作为业务决策的重要参考。有些CEO对数据的关注不够,而有些CEO想要查看数据,但依赖员工向他们提供报告,不过这些报告可能会提供得很慢或不完善。一个能够自信地查看与公司相关的数据的CEO是一个能够更好地发挥作用的领导者。如果CEO善于处理数据,当他们的商业知识与数据技能相结合时,可以更好地完成工作。同时,当数据科学家的数据知识与业务知识相结合时,他们将在工作中获得前所未有的突破,在业务方面取得更大的成就。
在本章中,我们从一个简单的业务场景开始:成为一名CEO并做出与业务运营和改进相关的决策。我们介绍了CEO需要做什么以及探索性数据分析如何对改进业务提供帮助。我们介绍了如何将数据读入Python、计算汇总统计量、绘制图表,并在业务环境中解释结果。在第2章中,我们将介绍线性回归,这是一种更复杂的方法,不仅可以用于数据探索,还可以用于预测。我们继续前进吧!