数据科学家:像生物学家一样思考!
生物机器学习的清单
这篇文章是为那些对生物学知之甚少或一无所知,但拥有数据科学技能并对研究生物领域问题感兴趣的读者写的。
我的目标是让你明白,不用读教科书或花 20 个小时在可汗学院学习,你就能解决这个问题。
请记住,虽然许多问题都被巧妙地预先消化到训练和测试数据集中,但情况并非总是如此。这篇文章是关于在你到达那个点之前帮助你的想法。
清单:
- 选择你的任务。理解它
- 理解解决你的问题的数据(通过现代生物学的数据类型
- 将你的项目放在上下文中理解你的目标。
- 最后,你应该如何以及为什么合作
选择你的任务。了解一下。
生物学是对活的有机体的研究。它是巨大的。接受现实吧。幸运的是,你的问题更具体。
这是一个有趣的问题,这是否是人类分类偏好的功能,但是如果你去维基百科查看生物学未解决问题的列表,你会看到每个问题都被整齐地分成不同的类别,比如“生物化学”、“神经生理学”和“生态学”。
重要的事情先来。不要认为你必须事先研究所有的东西,这通常是不必要的。根据需要了解更多信息。你的时间很宝贵。
本文的其余部分是关于如何将这些文献分解成对您的解决方案有帮助的有用见解。
如果你头脑中还没有一个问题,你可以到这里挑选一个问题。
学习一般技能最好是在可以找到具体答案的直接和真实问题的背景下进行。
David Clode 在 Unsplash 上拍摄的照片
现代生物学的数据类型
将我们的大部分时间花在训练和评估模型上是很诱人的。然而,在任何数据科学项目中,思考您的数据源和数据生成过程可能会有回报。
在生物学中,理解你的数据会有巨大的回报。
比较原始数据、注释数据和研究数据
原始数据是新的。它还没有被处理。
以 DNA 序列为例。这些信息包含了为所有生命提供蓝图的分子。我们用四个字母“A”、“T”、“C”和“G”的有序组合来表示它们。
尽管这些信件不是原始数据。事实上,我们通过实验技术来推断它们,这些实验技术涉及到溶液中 DNA 分子的反弹,这个过程有一些误差。因此,真正的原始数据实际上可能是在那个实验中产生的波状光谱。
因此,在现实中,数据的“原始性”是非常主观的,但在许多情况下询问这些数据是有价值的。
我们可以对比原始数据和注释数据。这些数据已经进行了某种程度的编辑。
详述前面的例子。我们可以使用类似字典的键将 DNA 序列翻译成蛋白质序列(由 20 个不同的字母组成)。
如果你看到一个来自 DNA 序列的蛋白质序列,这将是对 DNA 序列的一种注释形式,而不是与直接测序的蛋白质相同的数据(这确实来自其他分析过程)。
注释和其他领域一样,随着时间的推移会变得更加详细。
如果我们将研究定义为使用数据评估可能得到支持的假设的过程,那么研究本身可以被认为是一种极端形式的注释。
今年,呼吁使用自然语言处理(NLP)对可能与新冠肺炎相关的研究论文进行注释,使我们能够从现有文献中挖掘线索,以帮助应对全球冠状病毒。
这些项目的原始数据然后成为期刊文章中的文本,它本身描述了其他类型的数据。这里的是一篇示例论文。
使用数据类型作为描述生物数据的框架和语言是非常强大的,这些细节可以决定最终模型的解释和有效性。
观察对比实验
考虑你的数据来自哪里将决定分析的类型和你的洞察力的范围。在科学上,我们做实验。但是很多时候,我们也不会。
如果没有观察数据(出去观察动物之间的相似性),达尔文将如何证明进化?
反过来,当自然实验发生时,我们是不是应该忽略它们?(当自然随机地以不同的方式对待相同的实体时)。
实验数据源于有意改变一个或多个变量,观察世界如何变化。这种数据通常是用特定的分析方法创建的,如果要分析这种类型的数据,您需要理解这背后的逻辑。
实验数据之所以令人敬畏,是因为它常常能促成的因果推断。它通常非常适合监督学习任务。
观测数据常见得多。这包括我们没有系统地改变任何感兴趣的变量的任何数据。
用观察数据进行因果推断要困难得多,如果不是不可能的话。达尔文可能因为自然实验而成功(见上面的超链接)。
监督学习仍然适用于观察数据,但围绕模型解释的语言变得危险了。这在公共领域引起了很多关于 X 或 Y 是否对你的健康有益的困惑。
全球与本地
这种区分将可公开访问的数据与实验室或公司等特定团体收集的数据进行比较。我怀疑思考这种区别不仅仅是生物学上的应用。
全球 数据由大量关于分子、物种、医疗状况等的在线数据库组成,这些数据已被公开收集和共享。这些数据集是公共研究的重要存储库,并且可以通过链接丰富本地数据集。
学习如何利用全球数据资源可能是目前数据科学家处理大型非商业数据集的最令人兴奋的机会之一。
本地数据然而,是只有你或你的组织拥有的数据。它的结构可能与其他数据集相似,也可能不相似,或者使用相同的方法生成。
如果您试图在学术上或商业上与他人竞争,这些数据可能非常有价值,但如果您试图创建一个需要在许多环境中适用的通用解决方案,这些数据可能价值较低。
在本地数据上构建模型的危险在于,您的模型可能很难被其他人恰当地使用,除非他们能够重建您的数据收集过程。
进一步说明:本地数据不会因为可以在网上获得而成为公共数据,尽管这里的细节可能很重要。全局数据存储库通常在数据点之间创建关系,这意味着需要一个过程来向这些存储库提交附加内容。
由 Lavender888000 在 WikimediaCommons 上发布
背景是关键
数据科学的每个应用解决问题的标准都在变化,生物科学也是如此。
评估指标 对于任何项目来说都是重要的考虑因素,但是对于生物项目来说,重要的指标通常是特定于领域的(因此您应该了解领域!).
例如,在数据科学的许多生物医学应用中,错误发现率将构成您评估标准的一部分。如果你的算法说一个分子存在,而事实上它并不存在,并且一个研究人员相信你,他们可能会围绕这个结果建立他们的下一个研究!
模型解读 在生物问题中并不是微不足道的(曾经是吗?).取决于你如何构建你的模型,你也许能,也许不能解释它为什么做出这样的决定。有时候,这样可以,有时候不行。
模型解释可以简单到跟踪特征重要性(在基于树的模型中)、回归模型中的系数。通常,这些值可以直接与现有的理论或其他模型进行比较,从而将您的结果放在上下文中。
在生物领域,理解和交流模型的局限性是非常重要的。
数据科学从根本上受到数据的约束,所以当考虑你对你的模型的结论能说什么或不能说什么时,我会从那里开始。
您的数据在本地吗?还是分析了更大的全球数据集?后者将允许更大的普遍性。数据是实验性的吗?如果是这样的话,因果解释可能会摆在桌面上。
你用来训练你的模型的数据是如何注释的?这会影响你的模型吗?
理解领域和数据不是数据科学的附属品,而是了解模型能做什么和不能做什么的基础。
约翰·施诺布里奇在 Unsplash 上的照片
你必须合作
这不言而喻。
如果你想进步并接受挑战,最好的方法就是和不同的人一起工作。此外,如果这些人在你感兴趣的问题上有专业知识,这一点就变得尤为突出。
当我说有很多人愿意分享他们的专业知识时,我不认为这只是一厢情愿。
记住,一个能帮助你的人不需要博士学位,只需要比你现在拥有的更多的经验、知识或教育。
冒险,寻求帮助,分享问题。寻求帮助并不是对你性格的控诉,所以你真的没有什么可失去的。
后续步骤:
希望,当你努力去理解你问题的这些方面时,你会在一个更好的地方去解决它。
以我个人的经验,在质量动力公司,彻底确定问题和解决方案的标准是非常有用的。无论是对你的合作者、客户还是普通大众,努力理解你的问题领域将会创造真正的红利,并推动所有参与者的成功。
因此,做你通常会做的事情,处理数据,特征工程,特征选择,寻找潜在空间,创建嵌入,训练你的模型,评估和调整。做到这一切,知道你的目标是什么,真正的解决方案标准,并有一只援助之手引导你通过。
- *特别感谢本·哈珀等人帮助编辑本文。
随着量子计算机的发展,数据安全面临风险
量子位的兴起对加密意味着什么
维塔利·弗拉索夫在的照片
R 最近谷歌发表了一篇名为 量子至上使用可编程超导处理器 *的论文。*在这篇论文中,研究人员详细介绍了量子计算机如何能够在计算时间密集型任务中胜过经典计算机。具体来说,他们将最先进的经典超级计算机与其量子计算机在随机位串(0 和/或 1 的列表,如{0000101,1011100,… })采样任务中的性能进行了比较。谷歌的量子计算机名为“Sycamore”,处理器有 53 个量子位。处理器基本上是由 53 圈冷却到绝对零度以上百分之一度的电线组成。
Sycamore 处理器花了 3 分 20 秒完成采样计算。根据谷歌的说法,同样的计算将需要一台经典的超级计算机 10,000 年才能完成。IBM 回应谷歌的说法称,他们可以在 2.5 天内用经典的超级计算机模拟谷歌的结果。这里的警告是 IBM 需要使用橡树岭超级计算机,它是世界上最大的超级计算机。在《纽约时报》的观点文章 中,斯科特·阿伦森指出了谷歌成就的重要性,尽管 IBM 反驳道:
我们现在处于一个时代,在这个时代,经过巨大的努力,地球上最大的超级计算机仍然可能几乎模拟量子计算机做它们的事情。但是,今天这场竞争势均力敌的事实表明,这种势均力敌的局面不会持续太久。如果谷歌的芯片使用 60 个量子位而不是 53 个,那么用 IBM 的方法模拟其结果将需要 30 台橡树岭超级计算机。有了 70 个量子位,它将需要足够的超级计算机来填满一座城市。
经典计算机对量子计算机
为什么量子计算机比经典计算机强大那么多?
经典计算机使用数值为 0 或 1 的二进制数字。这些二进制值对应于两个级别的低 DC 电压(基本上是电压)。
量子计算机使用量子二进制数字(量子比特)。当你将电线中的电子冷却到 20 毫开尔文(非常接近绝对零度)时,就会产生量子位,这使它们表现出量子力学的行为。因此,量子位元,或我有意称为冷位元的东西,可以呈现 0、1 或两者的叠加(线性组合)状态。叠加态对应于获得二进制值 0 或 1 的相等概率。这意味着量子计算机具有“中间状态”,这使得它们能够捕捉像纠缠这样的量子行为。量子纠缠是指两个量子位即使在很远的距离上也有很强的相关性,以至于它们之间的通信速度似乎超过了光速。由于量子纠缠,量子比特可以存储更多的信息(通过超密集编码达到两个比特),并且比普通比特可以远距离传输更多的信息。纠缠是量子计算机比经典超级计算机更强大的核心。
什么是加密,它应用在哪里
加密是修改消息、文件和/或某些数据的过程,因此只有拥有加密密钥的特定人员才能访问原始信息。数据的预加密称为明文,数据的后加密称为密文。通常,通过随机数生成过程,使用一种算法从明文以及密钥中生成密文。一种常见的加密方法,也是最容易被量子计算机攻击的方法之一,是 Rivest–sha mir–ad leman(RSA)算法,它依赖于这样一个事实,即很难找到一个大的合数的因子。
加密技术可用于保护购物交易、电子邮件、网上银行记录、硬盘数据和密码。考虑到这些应用程序的高安全风险,如果开发出容易危及数据和信息安全的技术,这将是一个主要问题。
量子计算给加密带来了什么威胁?
在量子计算中,一种称为 Shor 算法的算法可以用来以比经典计算机更快的指数速度分解大的合数。这种能力意味着,通过一台足够强大的量子计算机,你可以轻松破解像 RSA 这样的加密系统。
IBM 保护量子计算机的数据加密
IBM 没有等待强大的量子计算机来保护我们所有的数据,而是开始开发保护量子计算机的加密系统。IBM 首先意识到量子计算机只擅长解决少数任务(其中之一是分解大的合数),从而着手解决这个问题。针对量子计算机保护数据的方法是使用量子计算机无法解决的问题来加密数据。这种问题的一个例子是“晶格问题”虽然自 20 世纪 80 年代以来已经研究了晶格问题,但是仍然没有对它们的经典或量子算法解决方案。这是摘自《科学美国人》的文章, 新的加密系统保护数据免受量子计算机 的攻击,关于晶格问题:
这类问题的一个简单例子是将一组五个数字中的三个加在一起,将总和给一个朋友,然后要求第二方确定哪三个数字被相加。“当然,有了五个数字,这并不难,”柳巴舍夫斯基说。“但现在想象 1000 个数字,每个数字有 1000 个数字,我从这些数字中挑选 500 个。”
虽然量子计算机对数据安全构成巨大风险令人担忧,但令人欣慰的是,目前正在研究数据加密的替代方法。虽然我个人认为谷歌的结果非常令人兴奋,尽管存在风险,但我认为为数据安全采取保护措施非常重要。随着技术的发展,我们不断产生更多的数据,这一点尤其如此。
在这篇文章中,我讨论了 Google 的 Sycamore 处理器,以及它在随机采样任务中在计算上超过经典计算机的能力。我讨论了量子比特的概念,以及纠缠态如何让量子计算机存储比比特更多的信息。这种存储更多信息和更快交流信息的能力使它在像合数因式分解这样的任务中胜过经典计算机。这对加密和数据安全有着巨大的风险。最后,我讨论了 IBM 针对量子计算机保护数据加密的努力。我希望这篇文章有用和/或有趣。感谢您的阅读!
数据农奴和标签数据验证:未来的挑战
前沿是更先进的模型还是当前模型更快更好的数据?
你走在你附近的一个城市,你问你的虚拟机器人朋友“我的家在哪里?”“我的家”是一家当地的中国餐馆。然后,你的虚拟朋友会问你是否迷路了,找不到回家的路,然后会给你提供真实的家庭地址。你真的很想找到那家餐馆。
你正驾驶着你的车绕着一个弯道行驶,它告诉你,当你可以明显地看到你做得很好的时候,你还没有完全在车道上。
你去看医生,后来收到保险公司的通知,说他们不会支付你的医疗费用。你很快就会发现办公室里的“某人”没有选择正确的诊断代码(截至 2018 年,共有 155,000 个可能的代码)。该办公室表示,一个新的自动化系统已经到位,该系统将通过电子方式读取医生的记录,并在未来防止这些问题的发生。你还没被说服。
这是怎么发生的?人工/增强智能(AI)的魔力失效了吗?或者更有可能的是,给餐厅数据或道路上的图像或医疗诊断代码贴标签的数据农奴大军那天表现得很疲惫,甚至那些负责验证标签(另一项数据农奴任务)的人也错过了错误。说到医疗编码,估计只有 63%的编码是准确的[。](http://A pilot study by the Healthcare Information Management Systems Society suggests that only 63 percent of ICD-10 coding may be accurate.)
尽管随着越来越多的数据被贴上标签,人工智能或增强智能的进步已经加快,尽管新的硬件设备和开源模型在很大程度上缓解了复杂机器模型中密集计算的成本,但当你探究为什么这么多对人工智能未来进步的预测落后于预期时,答案往往是,“是数据,傻瓜”。
随着我们对标记数据集的要求有了更深入的了解,还需要考虑标记者(仍然是人)的偏差以及标记过程的实际成本。标签的价格范围从 10 美元到 1000 美元,每 1000 个单位(或每件商品 10 美分至 1 美元)不等,并因多余的贴标机数量而异。当你像罗伯特·芒罗在这篇文章中所做的那样更深入地挖掘数据时,你会发现尽管这辆车可能是“她的”,但它更有可能被标记为“他的”,因为“在每个数据集中,男性代词的频率分别是女性代词的 3 倍和 4 倍”,然后你开始意识到,并非所有因偏见导致的错误最初都可能被视为错误。
作为建立和调整你的机器学习模型的过程的一部分,你决定花钱让其中一家数据标签公司给你的数据贴标签,有些公司像 Hive 吹嘘有超过 150 万名员工。当你将 Hive 的员工人数(临时或非临时)与亚马逊的约 70 万人和沃尔玛的约 220 万人进行比较时,所需工作的规模就变得更加清晰了。2019 年, 《经济学人》 引用了一个预测,“到 2023 年,数据标签服务市场可能会增加两倍,达到 50 亿美元”。如果您雇用一家公司的标签数据来构建您的初始模型,您是否应该雇用同一家公司来验证数据标签(无论是简单的输入验证还是确定模型在现实世界中是否如预期那样运行)?你应该雇佣自己的数据验证团队吗?有什么方法可以使这种验证自动化吗?把预测的和实际发生的进行比较?还是说这个精度不重要?
如果关于偏离车道的故事不是关于司机通知,而是关于一辆自动驾驶牵引拖车驶入你的车道,会怎么样?
…尽管是一个山口…
…在雪地里……
增强智能的要求不同于真正的人工智能,在增强智能中,提供可操作的数据,以便人类可以干预,而在真正的人工智能中,设备或车辆预计会自主行动。大多数增强智能依赖于大量精确标记的数据。因此,也许今天人工智能的真正前沿是数据标签和验证的规模化自动化,这可能会降低成本或改善环境,而不是更复杂的模型?
似乎很明显,要以自动驾驶汽车或设备的形式实现真正的人工智能,需要更高的预测精度和/或更好的上下文理解,这些汽车或设备可以在与生物相同的领域中运行。与此同时,值得关注的公司可能不是那些演示最棒的公司,而是那些致力于解决特定于其专业领域的规模化标签数据的公司。
数据来源越来越标准化;分析学、数据科学、ML 跟得上吗?
对标准模式开箱即用洞察机会的态度
叶 olde Salesforce 机会表!图片作者。
在过去的一年里,我在 Sisu Data 采访了 50 多位分析师、商业智能负责人和数据科学家。向他们中的任何一个人询问他们最大的痛点,答案都是一致的:从 ETL 工作到减少维度再到量化因素级别,使分析可行和有用所必需的准备、争论、转换和管理。
众筹的一项调查强调了这一点,该调查显示,数据分析师和科学家 70%的时间都花在数据准备上,而且(毫无疑问)被认为是工作中最不愉快的部分。总之,这是一个耗时且低价值的必要之恶。
但是我有希望:在接下来的几年里,我相信由于两个趋势,这种数据争论将减少到只占数据工作流的 20%。
- 第一个,已经发生了,涉及到在源和用例级别的数据标准化的增加。
- 第二个是即将到来的机会,包括 ML 生态系统能够在标准模式上提供现成的可操作的见解
对于第二种趋势,分析师和数据科学家将越来越多地承担创建开箱即用的洞察系统的任务,或者翻译、解释和业务用户授权的问题。
数据源标准化正在实现数据准备的自动化
SaaS 正在标准化模式。图片作者。
业务用例有两种标准化。一个是源代码级别的更强形式的标准化。另一种是基于通用数据结构而不是来源的较弱形式的标准化。两者都支持数据准备的标准化。
强大的标准化:占主导地位的 SaaS 供应商正在决定基础模式
如果说 SaaS 垄断带来了什么好处,那就是数据标准化。
在过去的 20 年中,Salesforce Opportunity 对象主导了公司看待渠道、赢得率和净新收入的方式。因此,它们的流行定义了跨行业销售和收入运营的标准数据格式。反过来,这种标准化创造了一个完整的公司生态系统,专注于最小化摩擦和最大化 Salesforce opportunity 表的价值。
我们在流量获取(谷歌广告、脸书广告)和金融交易(Stripe)方面看到了类似的趋势。即使是最后一个前沿领域——产品和 web 分析——也正在接近一个通用标准,Heap、Segment 和 Amplitude 等公司在事件日志数据的转换方面提出了自己的观点,同时在过去几年中获得了很高的市场份额。
Segment 提供了开箱即用的基本图表和可视化功能,可以轻松实现事件级日志记录。作者截图。
然而,由于可解释性和技术问题,源代码级别的数据标准化并没有像人们希望的那样带来有用分析的标准应用的繁荣。它为要应用的一致的有洞察力的算法和工具建立了框架,但是在最基本的图表之外,我们仍然需要分析师手动转换数据来为业务创建报告工具。这是因为标准仪表板可视化之外的用于转换和分析数据的工具仍然需要高级技术技能,即使这些任务变得可重复。我们离真正的洞察力还差一步。
标准仪表板可视化之外的用于转换和分析数据的工具仍然需要高级技术技能,即使这些任务变得可重复
弱标准化:数据结构、ETL、数据连接
如果数据源在用例间变得标准化,我们需要缩小的差距是标准化我们聚合数据的方式,并在不同的数据源间连接数据。例如,我们可能能够分析 Salesforce opportunity 表,但许多团队希望将 Salesforce 表中的数据与产品分析(例如,堆、幅度、细分)、营销属性和公司地理数据相结合,以便了解功能使用如何与追加销售机会或流失可能性相关联。
幸运的是,正在进行令人印象深刻的工作来消除这种摩擦。像 Fivetran 这样的公司正在进行 ETL/ELT 的标准化,five tran 只要求你指向一个数据源,并以自动化的方式吸收、加载和(与 DBT 合作)转换。这确保了跨不同数据源的连接也可以以某种标准的方式完成。
也许最弱的标准是关于业务用户的最终转变,或者是 ETLT 的悬空端。例如,仍然很难理解如何将事务聚集到合适的粒度或合适的时间窗口,以满足业务用户的特定需求。
但是伪标准的数据结构,即使没有单一的数据源,也使得一键自动准备的梦想成为可能。例如,在进行标准的营销漏斗分析时,将漏斗的每一步都列为一个字段,并用时间戳表示是否到达了该步骤,这正迅速成为一种最佳实践,为业务用户转换数据奠定了基础。
漏斗分析涉及将漏斗的不同步骤聚合成时间戳,并将特征附加到漏斗上的不同元素。作者截图。
又如,产品事件日志数据包含许多可能的来源,但结构大致相同。有些公司会从后端创建自己的 Kafka 流;其他的依赖于第三方,比如细分市场。但是事件日志结构在任何地方都是一样的。因此,减少摩擦的一个成熟领域是将正确的事件数据聚合到正确的时间框架中的非常常见且繁琐的分析。例如,用户可以查看每个用户在 90 天内点击“提交”的次数,或者用户在前七天点击“提交”的次数,或者用户在第一年发表评论的次数。
有了云的无限可扩展性,为什么不在不编写任何 SQL 语句的情况下,自动聚合多个不同时间段的多个不同事件呢?
如果业务数据正在变得标准化,为什么吐出一个有趣的见解仍然如此令人沮丧?
概括一下,这里有四个地方正在进行标准化
来源:作者
但是,即使没有分析堆栈的所有部分的标准化,从我们漂亮的、高价值的 Salesforce 机会表构建甚至最简单的模型也是令人沮丧的繁琐。
对于 ML 和复合建模来说,最大的机会是将分析和 ETL 融合在一个统一的步骤中。
为了说明今天的问题,让我们问 Salesforce opportunity 表中最简单的业务问题—与高成交率的业务机会相关的是什么,这样我就可以专注于这个分组了。在 2020 年,我希望能够用一行程序将它插入到一个工具中,并立即得到答案。
在我的例子中,我想在桌面上运行一个快速的套索来理解看起来最重要的特性。LASSO 是一种很好的技术,因为它通过损失函数进行正则化,使得噪声系数变为零。换句话说,LASSO,如果我有一堆数值变量,会突出最有影响力的变量。但是很快,我一个接一个地遇到了许多令人沮丧的事情,尽管成千上万的数据科学家最终在任何地方都编写了相同的预处理逻辑:
吸收
很多时候,数据集可能太大而无法打开,许多云工具在 10GB 左右的上限时失败(我们的 Salesforce 表很小,但请耐心等待一会儿)。然后,数据科学家必须采样,以便在本地 Jupyter 实例中打开它。
但是当最终结果依赖于拥有整个数据集时,它就开始崩溃了。例如,管理人员希望看到一段时间内的总销售额或总“成交金额”计数。在这种情况下,抽样显然会导致严重的少计。在这些情况下,为了减小规模,分析师将求助于构建数据立方体或运行 GROUP BY。不幸的是,一旦某些东西变成立方体格式,就很少有工具可以利用 ML 建模的最新进展。
一些工具,如 Anyscale 和 Ray,开始关注数据堆栈的这一部分,从而在任何规模的数据集上实现无限的可扩展性。只是看你想出多少钱。
幸运的是,我们的 Salesforce 表很小,但是如果它很大,我甚至很难获得数据。
转换和聚合
假设我们的 Salesforce 数据集足够小,可以装入笔记本电脑。同样,我特别感兴趣的是了解什么与高成交率的机会相关联,并且喜欢使用 LASSO 进行功能选择。但是,我们注意到该模式包含了大量的非数字字段。
叶 olde Salesforce 机会表!来源:作者。
所以我现在的自然选择是一次性编码分类特征,这会导致稀疏性和多重共线性的问题,这需要更多的 munging。这甚至没有检查富文本字段。尽管现在开箱即用的软件包使处理这些问题变得更加容易,但产品经理或注重数据的商务人士没有时间学习开源 scikit 软件包。
此外,世界在不断发展,只需点击几下鼠标,就能按需获得“见解”,而不是多行脚本。
我正在想象一个点击式工具,本质上是“Salesforce 模式的套索”或“Python 系列数据的套索”。它可能已经在某些地方投入生产,我迫不及待地想让它成为标准。
我们甚至还没有探究我过去提到的事件日志数据。首先,要生成的特征的数量几乎是无限的。LASSO 有助于从列表中选择功能,但不是生成本身:我想看看注册后前 7 天的登录次数吗?还是付款后第一天的总会话持续时间?在没有任何机器帮助的情况下浏览最重要的功能,我只能依靠我的直觉,或者我的商业用户可能已经有的任何偏见。
解释
到了这一步之后,我得到了一个具有统计显著性系数的向量。
学生项目的 GLM 输出。作者截图。
但是想象一下将这一点转化为你的商业利益相关者!相反,我必须回到 SQL,让它更容易理解。在我的例子中,我确定了一个系数,它似乎具有非常低的 p 值,即使在正则化之后也是一个合理的系数:B2C = 1,BI_Tool_looker = 1。然后,我简单地查找了出现的系数的转换率(在这种情况下,B2C 销售机会的转换率特别高),并显示了转换率。
在所有这些工作之后,在浏览了给团队留下深刻印象的所有 p 值和系数之后,这实际上只是一个见解:
- 使用 Looker 和新的轻量级销售流程的旧金山 B2C 客户进入销售漏斗晚期的可能性增加了 3 倍。
就为了这个?哦!在这一点上,我只是累了。
早期的一线希望
随着数据世界中出现的所有标准化,如果数据科学工具和供应商可以将标准数据与标准的多阶段模型相匹配,情况会怎样?如果我们能够让业务用户直接从几个标准数据源获得洞察力,会怎么样?
Alteryx 是一家朝着这个方向前进的公司,它能够将 ETL 和多阶段建模集成到一个 DAG 中。
Dag,如 Matillion 中的这个,支持点击式数据准备。Alteryx 也支持作为 DAG 的一部分的 ML 模型创建、训练和生产化。作者截图。
如上所述,更改模式、创建正确的字段、进行聚合,然后进行逻辑回归都是通过拖放点击来完成的。但是,在一个数据正在成为标准,所有业务用户都必须由数据驱动的世界里,即使是 DAG 也可能不堪重负。
在 Sisu Data (我在 Sisu 负责解决方案和数据产品),我们相信即使是这些配方也可以简化。我们的愿景是,对于某些数据结构—产品事件日志、Salesforce 机会—您不应该需要 DAG 或 Python 甚至 SQL 来获得洞察力。在处理上面的 Salesforce 表时,我们有一个多阶段模型,分阶段对稀疏性、共线性和文本解析建模;甚至使输出具有高度的可解释性。
随着业务用户要求洞察,而不仅仅是报告或图表,对于标准数据结构上的垂直化、自以为是的工作流来说,这是一个巨大的机会,只需点击几下鼠标,就可以从源直接洞察
由于业务用户需要洞察,而不仅仅是报告或图表,因此标准数据结构上的垂直化、自以为是的堆栈存在巨大的机会,可以扩展从来源到洞察的路径:
- 接收:云仓库的出现使得接收 TB 级数据集不再需要一个超负荷工作的数据工程师团队。相反,这应该像业务用户的对话框一样简单。
- 机器辅助聚合:跨越时间窗口的交易事件日志等原始数据的聚合应该成为推荐,而不是令人眼花缭乱的各种可能性。
- 机器辅助连接:有了一些标准的数据源,我们可以建议跨不同数据源的有趣的转换和连接,机器会智能地选择最有意义的数据。
- 建模:对于标准的连接和模式,一个解决方案可以跨多个模型应用 80/20 解决方案,这些模型能够以可能的最佳方式提供可解释的洞察力,而不是多阶段模型。
- 洞察与解读:不再有系数或可变等级重要性图表。输出应该是可解释的,并且是商业用户期望的那种简洁的见解,就像脸书著名的“10 天 7 个朋友”的见解。
这给分析师和数据科学家带来了什么?如果来源的标准化导致流程的标准化和自动化以获得洞察力,数据科学家将可以自由地深入到开箱即用的洞察力不再起作用的长尾案例中。分析师将越来越多地发现自己扮演着类似 PM 的角色,引导洞察力的流动并将其转化为行动。两者都将摆脱“工作中最不愉快的部分”,并在最需要和最有趣的地方增加商业价值。
适合任何机器学习模型的数据分割技术
将数据分成不同类别的目的是避免过度拟合
这是一篇 4 分钟的短文,向大家介绍数据分割技术及其在实际项目中的重要性。
从伦理上讲,建议将数据集分为三部分,以避免过度拟合和模型选择偏差,称为-
- 训练集(必须是最大的集合)
- 交叉验证集或开发集或开发集
- 测试设备
测试集有时也可以省略。这是为了获得真实世界中算法性能的无偏估计。将数据集分成两部分的人通常称他们的开发集为测试集。
我们试图在训练集上建立一个模型,然后尽可能地优化开发集上的超参数,然后在我们的模型准备好之后,我们试图评估测试集。
#训练集:
用于拟合模型的数据样本,即我们用于训练模型的数据集的实际子集(在神经网络的情况下,估计权重和偏差)。该模型观察并学习这些数据,并优化其参数。
#交叉验证集:
我们通过最小化交叉验证集上的误差来选择适当的模型或多项式的次数(如果仅使用回归模型)。
#测试集:
用于提供最终模型在训练数据集上的无偏评估的数据样本。只有在使用训练集和验证集对模型进行了完整的训练后,才使用它。因此,测试集是用于复制一旦模型被部署用于实时使用时将会遇到的情况类型的测试集。
测试集通常用于评估 Kaggle 或 Analytics Vidhya 竞争中的不同模型。通常在机器学习黑客马拉松中,交叉验证集与训练集一起发布,实际测试集仅在比赛即将结束时发布,并且是模型在测试集上的分数决定了获胜者。
#如何决定分割数据集的比例?
图:数据集分割,源-在infogram.com上制作
答案通常在于数据集本身。比例是根据我们可用数据的大小和类型(对于时间序列数据,拆分技术略有不同)决定的。
如果我们的数据集大小在 100 到 10,000,000 之间,那么我们以 60:20:20 的比例对其进行分割。也就是说,60%的数据将进入训练集,20%进入开发集,其余的进入测试集。
如果数据集的大小超过 100 万,那么我们可以按照 98:1:1 或 99:0.5:0.5 这样的比例进行分割
决定分割比的主要目的是所有三个集合应该具有我们的原始数据集的一般趋势。如果我们的开发集只有很少的数据,那么有可能我们最终会选择一些偏向于只存在于开发集中的趋势的模型。训练集的情况也是如此,太少的数据会使模型偏向于仅在数据集的该子集中发现的某些趋势。
我们部署的模型只不过是学习数据中统计趋势的估计器。因此,用于学习的数据和用于验证或测试模型的数据应尽可能遵循相似的统计分布,这一点非常重要。尽可能完美地实现这一点的方法之一是随机选择子集——这里是训练集、开发集和/或测试集。例如,假设您正在进行一个人脸检测项目,人脸训练图片来自 web,而开发/测试图片来自用户的手机,那么在训练集和开发/测试集的属性之间将存在不匹配。
将数据集划分为比率为 0.6、0.2、0.2 的 train、test、cv 的一种方法是使用 train_test_split 方法两次:
from sklearn.model_selection import train_test_split x, x_test, y, y_test = train_test_split (x_train,labels, test_size=0.2, train_size=0.8 )x_train, x_cv, y_train, y_cv = train_test_split(x,y,test_size = 0.25, train_size =0.75)
这篇文章就写到这里吧——如果你已经做到了,请在下面评论你的阅读经历并提供反馈,还可以在 LinkedIn 上加我。
类似文章— 深度学习变得简单:第 3 部分:激活函数、参数和超参数以及权重初始化
面向娱乐和非营利组织的数据堆栈—第二部分
使用 Meltano、dbt 和超集设计完整的数据堆栈
在我的上一篇文章中,我探索了一些廉价的选项来为训练练习和附带项目组装您自己的数据仓库。从那以后,我为这个系列想出了一个更巧妙的标题:有趣的数据栈&非营利组织。我正在扩展本系列的范围,以涵盖整个数据栈,而不仅仅是仓库组件。上次,我解释了数据堆栈往往由三个组件组成(ETL、DW 和 BI 组件),对于 ETL 和 BI 组件,您的基本选择是 DIY 或面向企业客户定价的商业平台。
当然,我说得太早了,因为事实证明,在 ETL 和 BI 前沿都有一些非常有趣的开源开发!我将在本文中深入探讨这些,但结果是,我相信我们现在可以从免费增值和开源组件中组装一个真正的端到端分析堆栈。因此,本系列将面向那些希望建立自己的“DS4FNP”堆栈的人,无论是作为个人辅助项目、教学资源,还是无法在企业预算内运营的非营利组织。
我们完整的数据堆栈!**(左)**图片由梅尔塔诺 | **(中)**图片由 dbt | **(右)**图片由 Apache 超集。
在本文的其余部分,我将从我上次探索的 DW 选项中报告一些结论,特别是通过dbt
的视角。然后,我将介绍一些我认为是完整的开源 ETL 和 BI 选项。最后,我将通过描述如何设置一个完全本地的开发环境,以及为托管的生产环境提供一些选项来结束本文。
我之前确定了构成我们堆栈核心的三个候选数据仓库平台:BigQuery、Snowflake 和 Athena。为此,我对所有三个平台的结论如下…
雪花,我有一些折扣,因为它可能是三个中我最喜欢的,它不提供任何形式的免费增值定价模式。尽管是“随用随付”,你仍然需要支付一些费用。然而,事实证明你实际上把你的成本降到了每月 25 美元。现在,虽然这是一个相当低的月成本,但我仍然不知道它是否低到足以证明个人项目的合理性。但是如果这个项目对我个人来说足够重要,并且我觉得我可以最大化我的付费资源使用的效用,我可能会咬紧牙关付费。雪花真的很棒。也就是说,在令人兴奋的首次公开募股之后,如果能看到雪花发布某种专门针对小项目、培训等的有限免费增值模式,那真是太好了。
BigQuery 无疑是最吸引我的。设置帐户和 BigQuery 实例是一个非常简单的过程。 [dbt](https://docs.getdbt.com/tutorial/setting-up/#create-a-bigquery-project)
中的配置同样简单,甚至认证设置(我经常认为谷歌 IaaS 传统上让这个过程变得不必要的复杂)也是轻而易举。在本系列的大部分时间里,我很可能将重点放在 BigQuery 上作为目标仓库,同时尽可能保持仓库无关性。由于设置简单,其出色的定价模型(如果需要,很容易免费),以及可用的集成数量,BigQuery 不会出错。
雅典娜和另外两个有点不同。它的主要优势是:1)它完全在你的控制之下,在你自己的 AWS 账户之内;2)如果 AWS 是你首选的生态系统,它是最便宜的选择。Athena 的主要问题是你必须自己完成所有的设置。我认为,在 Terraform 或 Pulumi、的帮助下,这可以成为一个更全面的解决方案,这当然是我以后想要探索的事情。还有一个dbt-athena
插件,尽管我发现文档仍然相当原始,我仍在努力克服配置障碍。随着时间的推移,我认为 dbt 插件和 Athena 供应脚本的成熟将使 Athena 成为 DS4FNP 的一个非常好的仓库选项。与 S3 的本地互操作性以及 Presto 的潜在抽象性使得 Athena 成为一个真正理想的仓库目标。
但是我们实际上在选择云仓库平台方面有点超前了。在开发您的分析项目时,您实际上可以从本地 PostgreSQL 或 SQLite 数据库开始,当然还有dbt
。事实上,使用dbt
,您可以为我们讨论过的任意数量的仓库以及我们的本地环境设置单独的连接概要文件。这意味着我们需要开始的只是我们的笔记本电脑、Python、dbt
和我们本地的“仓库”(postgres
或sqlite
)。在本系列的后面部分(可能是第三部分),我们将开始实际整理一个完整的教程,从头开始设置我们的项目。
好吧,那么我之前提到的这些开源 ETL 和 BI 平台呢?我有充分的理由认为,这些组件只有作为昂贵的企业服务或耗时的 DIY 项目才真正可用。ETL 通常计算成本相当高,所以没有提供免费增值定价的强烈动机。你在那里的商业选择包括 Fivetran、Stitch、Matillion 和其他几个。从 Python 脚本中运行您自己的 ETL 在理论上可能看起来很简单,但是大多数真实世界的 ETL 任务最终会陷入两种类型的复杂性中:
- 结构复杂性,意味着从复杂的数据库模式和 API 规范中加载数据,尤其是当这些结构随时间变化时。
- 容量复杂性,意味着加载需要流或批处理的大容量数据源。这主要与日志数据有关。这里的复杂性主要不是来自解析或转换逻辑,而是来自移动大型数据所涉及的基础设施逻辑。
不管是哪种情况,我认为人们倾向于低估 DIY ETL 所涉及的长期努力。这是一个很容易陷入的陷阱,因为从 10,000 英尺外看,这似乎是一个简单的“脚本化”任务:只需从一个源获取数据,然后将其发送到某个地方。不过,这种模式也有一定的道理,您经常会发现自己在重复相同的基本过程,只是有足够的变化来保证某种程度的抽象,尽管还不足以证明自己有理由进行抽象;这种类型的问题只需要一个标准化的框架,但长期以来,这些框架都被锁在商业平台的后面。
图片由梅尔塔诺拍摄
幸运的是,开发人员可以使用这样一个框架:歌手,由史迪奇开发,是一个用于开发“taps”和“targets”的可组合框架。Taps 从数据源提取数据,包括数据库、API、日志、电子邮件等。目标是 taps 提取的数据的目的地,通常是常见的数据仓库(雪花、BigQuery 等)。每个 tap 将其数据提取到一个公共的中间体中,每个目标从该公共的中间体中进行加载,从而允许人们组合 tap 和目标的任意组合来形成 ETL 任务。
Singer 不一定是新的,但也不一定能为我们的需求提供完整的解决方案。Singer 为标准化我们的 ETL 工作流的组件提供了一个很好的起点,但是它仍然需要大量的配置管理和编排。然而,这个明显的差距已经被一些新进入该领域的人填补了,其中有梅尔塔诺(来自 GitLab)和 Pipelinewise (来自 Transferwise)。这两个工具都是在 Singer 的基础上增加了一个配置管理和编排层。它们还允许您将提取/加载任务作为代码来管理,就像 dbt 允许您将转换模型作为代码来管理一样。我已经尝试了它们,我都喜欢它们,我有点希望这两个项目以某种方式合并或互操作,因为它们都有不同的优势,实际上是互补的。Transferwise 似乎更专注于开发一套符合特定标准的可靠歌手 tap/targets,而 Meltano 则更专注于填补所有空白,将 Singer 付诸实践,作为 Fivetran 和 Stitch 等平台的合法替代品。因为 Meltano 与我们 DS4FNP 项目的任务更相关,所以我将主要关注它。
Meltano 管理的项目的基本元素是提取器(Singer taps)、加载器(Singer targets)、转换器(dbt!),以及编排(默认为气流)。仅凭 Meltano,您几乎可以管理您的整个分析项目。到目前为止,我已经完成了它的文档教程和设置项目。这感觉很像 dbt,虽然推出自己的生产部署似乎足够可行,但我非常有兴趣看到托管的 Meltano 即服务进入场景。Meltano 是很好的 Dockerized,我可以很容易地看到通过 AWS 批处理或类似的基础设施运行它。
在 BI 方面,你的商业平台包括 Tableau、Looker 和 PowerBI 等。雪花现在有内置的 Snowsight,BigQuery 与 Data Studio 很好地配对,Athena 的模拟伙伴将是 QuickSite。大型独立平台真的缺乏任何实用的 DS4FNP 友好定价选项,而 DW 特定选项对于该项目的精神来说感觉有点过于供应商锁定。到目前为止,这是整个堆栈中最主观的组件,因此无论如何,您应该在这里插入自己的偏好。BI 硬币的 DIY 面也提供了一些值得注意的选项,包括 R 的 Shiny 和 python 的 Dash。比起 ETL 工作流,我更不厌倦制作自己的 BI 接口。
图片来自阿帕奇超集。
然而,为了形成一个完整的堆栈,我们确实有一个 BI 层的理想选择。超集也不一定是新的,但它肯定在加速发展,现在它是 Apache 的一个项目。由气流创造者 Max Beauchemin 开发的 Superset 很快成为 Tableau 的开源替代方案。从美学上看,这感觉像 Tableau 和 Airflow 有一个开源的爱子,但内置的 SQL 实验室也感觉有点像 Snowsight 或 Redash 。通过docker-compose
很容易部署,超集可以作为我们的 DS4FNP 开发环境的一部分在本地运行,也可以作为长期运行的 web 服务部署。
至此,我们的 DS4FNP 堆栈由以下组件组成:
- Meltano 通过 Singer、dbt 和 Airflow 提供我们的 ETL (ELT)层。
- 我们的 DW 层将在本地使用 PostgreSQL 或 SQLite,在云中使用 BigQuery。
- Apache 超集作为我们的 BI 层。
简单说一下环境吧。这个堆栈的伟大之处在于,我们可以轻松地在本地运行它的每个组件,但我们还需要最终确定我们希望如何在云中部署。
我认为我们可以在一个单一的存储库中组织我们所有的代码,这主要归功于 Meltano,他为我们管理 dbt 代码和 Airflow dags。在 Meltano 和 Superset 的 Docker 部署以及任何仓库基础设施供应代码之间,我们可能希望返回到通用基础设施存储库(或者不返回)。出于教程的考虑,我可能会使用 SQLite,这样项目的每个有形组件都是受版本控制的。
我使用 Mac,所以我的一些本地设置可能是特定于 Mac 的,但我会尽量做到平台无关。我是 VS 代码的忠实粉丝(它现在有一个[vscode-dbt](https://marketplace.visualstudio.com/items?itemName=bastienboutonnet.vscode-dbt)
插件!),尽管 Atom 或任何其他 IDE 也同样适用。Meltano、dbt、Singer、Airflow 和 Superset 大部分都是 Python,所以如果本地(非容器化)运行这些,我们需要 Python 和 Anaconda 或类似的设置。我们还需要确保安装了 git 和 docker。至于本地数据库,我们要确保我们安装了 SQLite 和 PostgreSQL,对于后来的 Mac,我强烈推荐 Postgres.app 以及 Postico 或 TablePlus 作为 GUI 客户端。大部分工作将使用各种 CLI 工具来完成,所以要确保有一个舒适的终端设置,或者使用 VS Code 的内置终端。我个人用的是 VS 代码对 iTerm2 与 Oh-My-Zsh 的直通。
云部署将取决于几个因素。我强烈建议用 Github 或 GitLab 在 git 存储库中管理您的项目代码。然后,您可以设置 CI/CD 工作流(通过 Github Actions、Travis CI 等。),这有助于自动化所有的部署管理任务。我倾向于使用 Github,我发现 Github Actions 比一个独立的 CI/CD 服务更实用,但是其他人可能会有不同的看法。现在,当我们考虑项目的云部署时,有一系列事件涉及几个不同的移动部分:
- 我们希望定期运行我们的提取和加载任务,可能由一个定义的时间表决定,这样做可能需要通过 AWS EC2、GCP 等产生一些可伸缩的执行主机。
- 我们还希望定期运行我们的转换代码,尽管因为转换代码大多只是远程执行的 SQL 命令,所以我们的执行主机更容易推广到标准大小的主机。
- 我们的数据仓库显然需要存在于某个地方,但当前下一代系统的美妙之处在于它们将计算和存储分开,并管理两者的协调。实际上,dbt 是我们与仓库实际内容的主要接口。
- 虽然我们的提取、加载、转换和仓储功能都可能是短暂的批处理过程,但不幸的是,我们可能无法避免将 BI 层作为长期运行的 web 服务。这意味着要为 24/7 服务主机付费。如果你对支付托管服务不感兴趣,我们可以简单地提供预先生成的静态内容作为我们的 BI 层(这是完全可能的)。
Github 或其他 CI/CD 服务可以托管我们的部署流程,但是对于批处理流程和长期运行的服务,我们都需要托管基础设施。因为几乎所有东西都是容器化的,所以我们可以在我们自己的自动工作站或 GCP 基础设施上部署任何这些流程。另外值得注意的是,dbt 提供 dbt-cloud , 天文学家. io 提供优秀的气流托管服务, Preset.io 是即将推出的超集托管服务。唯一缺少的是 Meltano,似乎可以想象一个类似的托管服务将会出现。我们完全有可能最终能够在一个免费增值定价模式上托管我们堆栈的每个组件。
原来如此!我们有自己的数据堆栈架构。对于本系列的下一部分,我想通过一个更正式的教程来介绍如何使用所有这些组件来设置一个项目,并最终实现一个填充的数据仓库和一些动态数据可视化。在此之前,我绝对推荐阅读 Meltano、dbt 和 Superset。我还需要为 DS4FNP 教程提供一些数据集,所以请在评论中提出任何建议或请求。与此同时,你也可以享受由 Preset.io 团队编写的本教程。下次见!
数据故事:电话
根据我一月份的电话通话数据,我 73%是婴儿潮一代…
👋你好。我又回来参加一个月有趣的数据活动了!上个月,我查看了我在 2019 年乘坐的所有次飞机航班,这个月我决定查看我在一个月内打了多少电话。(主要是因为朋友们很难相信我说打了多少电话,想看看数据怎么说)。
我花在电话上的每一分钟都被映射成 24 小时周期。如你所见,时间并没有真正的韵律或理由,除了我显然对晚上 10 点睡觉非常挑剔的事实
👴等等什么??
你知道那句老话吗,数据不会说谎?☝️That 的数据(是的,那个说我 73%是婴儿潮一代的数据),那是个谎言(嗯,也许吧🤔).关于人们每天在非工作原因上花多长时间打电话,没有一致的数据,但在一月份,我在 214 个电话上花了【214 小时 20 分 7 秒。这意味着在这一个月里,我可以看两遍《权力的游戏》第八季,而不是打所有的电话,并且还有几秒钟的空闲时间。(不过不用担心,我没那么讨厌自己😂)
在我接的 214 个电话中,137 个是拨出的,77 个是拨入的,只有 4 个是垃圾邮件(我知道,我也对此感到惊讶,我猜在联邦禁止通话名单上有所帮助😂).有些人在我给他们打电话的时候肯定会更好地接电话,但是在我所有未接的电话中,我又花了 10 分 51 秒等着看他们是否会接电话🙃
我追踪到的
与上个月相比,我这个月跟踪了相当有限的数据,但仍然看到了一些超级有趣的事情。
- 一般电话通话信息:通话日期、时间、通话方向、通话持续时间、通话时间、我给谁打了电话
- 其他信息:他们接了吗?,我接了吗?,电话标签?(我听过的唯一一个说这话的人是我妈妈(请继续关注她为什么使用这个短语),但它基本上是衡量我们是否交换了 3 个以上的未接电话),我和谁说话(这是衡量电话是否被传递的一种方式)
我没追踪到的是
- 我打电话的时候在哪里。(我的家人取笑我,因为我开车时往往会打很多电话,但我没有跟踪,因为这需要近乎即时的数据跟踪,以免忘记我是否在开车,这有点不安全)
😴我有多“基础”??
我非常好奇的一件事是,我的电话是否有时间表,我是否在相同的时间给相同的人打电话,我的电话数量是否在工作日或工作日的晚上增加,以及所有这些类型的事情。
进入这个月,我有兴趣看看我是否在周末比工作日打更多的电话——在某种程度上,我认为我会在周末打更多的电话,但也考虑到工作日比周末多 3 天的事实……当我开始提取数据时,我意识到有几天我不工作,所以我将我的想法转移到工作日而不是工作日。当我这样做时,我们看到的是,我确实在工作日打了更多的电话,而且几乎都是在晚上 6 点到 8 点之间。在非工作日,我更加多样化,但仍有一些时段我更有可能打电话(事实上在下午 5 点左右有一些重叠,但在早上有明显更高的波动)。尽管我在工作日的晚上打了很多电话,但我平均打了一个电话,从下午 4:18 开始,持续了 4 分 53 秒。
除了周日,我惊讶地发现我的通话次数和平均通话时长在整个星期都保持不变——我原本预计会看到更多的跳跃,就像我们在第二张图中周一看到的那样。
☎️我管这个叫谁多??
这个月我非常好奇的一件事是,我的拨入电话和拨出电话的比例是否与每个人持平——所以从某种意义上看,人们是否“公平地”打电话给我,或者他们是否让我发起大多数电话。我发现我的大部分电话都是拨出的,但并不像我想象的那样偏斜。
我更有可能打电话,但总体来说并不严重。
同样的,我想知道是否有我一直不接电话的人,或者相反,我发现妈妈是我打电话时最忙的人(或者她只是最不爱我)🙃)但根据我给他们打了多少电话,人们的未接电话似乎相当一致(见上文),尽管我姐姐说我在她打电话时不接电话时颇有怨言,因为她总是接电话。
我妈妈和爸爸上画的圆圈是我在洛杉矶拜访他们的日子,我很好奇为什么我仍然和他们通了 3/5 天的电话。
这让我想到了我和我的直系亲属通话的日子,以及这些电话发生的日子是否有什么规律。事实证明,没有,但有趣的是,我并没有在周三或周四给我的姐妹们打电话——除了在月末,我连续两天给她们打电话。
最后,我想看看整个月我都在和谁打电话,至少从通话时长的角度来看是这样的。我以为我会和我妈妈说得最多,因为我几乎每天都和她说话,但事实证明我和我的姐妹们说得最多——尽管不是每天都和她们说话,但我们的平均通话时间要长得多,这样加起来。
📵TL;博士:
一月份,我花了大量时间打电话——主要是和我的直系亲属,但我一视同仁,整个月和 33 个不同的人通话。总共,我在电话上花了 14 小时 20 分 7 秒(相当于从波特兰,或者我住的地方到阿根廷布宜诺斯艾利斯的飞行时间)。当我打电话时,并没有真正的韵律或理由,也没有一周中的一天的异常(除了周日,我不知道我整个月的每个周日都做了什么…)
我要去计划未来的旅行或类似的事情,下个月我会带着一些东西回来(理论上)。如果你对你希望我跟踪的事情有任何想法,请随时在 LinkedIn 上给我发一条消息(请不要给我打电话,那太元了……)
用数据讲故事项目
使用 Python 的 Matplotlib 和 Seaborn 包进行数据可视化
我的项目的样本图表。
D 数据可视化通常服务于两个目标之一:呈现或探索数据。这里我依赖于前者,并使用 python 的 Matplotlib 和 Seaborn 包的组合来完成。
这篇文章将简要概述我在实习委员会的数据分析师项目中的发现。请继续关注第 2 部分,我将向您展示如何创建用于本项目的类似图形。
这项任务
你决定在洛杉矶开一家小型机器人咖啡馆。这个项目很有前景,但是很昂贵,所以你和你的合伙人决定尝试吸引投资者。他们对当前的市场状况感兴趣——当机器人服务员的新鲜感消退后,你还能保持成功吗?
得出总体结论,并对餐厅类型和座位数量提出建议。介绍你的研究,与投资者— 目标受众分享。
数据描述
洛杉矶餐馆的开源数据。点击此处此处下载数据集。
- 对象名称—机构名称
- 链-链的建立(对/错)
- 对象类型—机构类型
- 地址—地址
- 数量——座位数量
洛杉矶流行什么类型的场所?
洛杉矶大约有 3/4 的餐馆是正规餐馆,其次是快餐(约 10%)。
当查看统计数字时,我们看到洛杉矶有 7255 家机构属于餐馆类型。相比之下,咖啡馆(拟议中的机构)只有 435 家,占全部机构的 5%。在此基础上,我们可以看看非连锁商店与连锁商店的比例。
连锁和非连锁企业的比例是多少?
在目前的洛杉矶市场中,小型企业(非连锁机构)占有很大的份额(约 2/3)。
哪些类型的企业是典型的连锁店?
咖啡馆成为连锁机构的几率更大。此外,大约 1/3 (31.6%)的餐馆属于连锁店;这可能就是为什么非连锁店在饼状图中出现的原因。
这一数字与 Allegra World Coffee 进行的一项深入研究(5000 项调查和 100 多次采访)的结果相一致,该研究表明,近五分之四(78%)的咖啡店是连锁机构,而且这一比例还在不断上升。
这些数字表明,从长远来看,开餐馆比开咖啡馆更为明智。
连锁餐厅的特点是什么:许多餐厅座位少,还是少数餐厅座位多?
该图向我们展示了每个机构 25 个座位的中位数,连锁倾向于拥有许多座位数量少(1-50 个座位)的机构,这是约 82%的机构的特征。
哪种连锁机构类型,相对于它们的总数,具有“许多座位”的机构最集中
因此,对于连锁机构来说,每种机构类型的少数座位比例都在 80%以上。
请注意,该图显示了机构类型的“多座位”比例,其中餐馆的份额最大,这可能是将总计“少座位”比例降至 80%的原因。
此外,餐馆设立的席位百分比很低,这表明洛杉矶有许多小型连锁餐馆。让我们继续探索
哪个连锁餐馆有最多的座位?
当我们考虑总平均数时,请注意 4 种机构类型的平均席位数是如何比它低 10 个以上的。
座位分布(见上图)上的大多数“许多座位”值可归因于餐馆和酒吧机构。考虑到前面的图表,连锁餐厅的平均座位数似乎是公司可以开始的合理座位数。
哪些街道的设施密度最高?
这些街道包含超过 110 家和多达 325 家的机构。进一步的分析表明,68%的街道包含一家餐馆,而 32%的街道包含不止一家。
如果公司想进入一个竞争激烈但蓬勃发展的市场,他们可能会对这些街道感兴趣。
推荐
考虑在洛杉矶开业时,考虑到(1)所选的店(小咖啡馆),(2)所提供服务的新颖性(机器人服务员),(3)当前的市场条件,(4)项目的成本,以及(5)维持成功开业的长期目标,给出以下建议:
- 公司应该考虑开一家餐厅而不是咖啡馆,因为大玩家在餐厅市场占据的空间更小。小型咖啡馆正在被连锁咖啡馆击败。
- 该公司应该考虑从一个有 48 个座位(连锁餐厅的平均座位)的独立机构开始,并根据其战略,选择一条有许多餐厅或一家餐厅的街道。然后,它可以基于其机器人服务员方法的新颖性,积极发展并建立一个连锁店。
结束语
作为一个编程领域的新手,几个月前,创建上述图表超出了我的能力范围。当然,我可以用 Plotly 之类的库,但是我真的想了解图形的结构。Matplotlib 和 Seaborn(构建于 Matplotlib 之上)帮助我实现了这一点。
就我个人而言,我会说这是一个具有挑战性的项目,它教会了我如何思考观众、背景以及我在创作上述图表时试图讲述的故事。也是通过这个项目,我爱上了数据可视化,并很高兴能继续实践和学习这个广阔的创造性领域。
无论如何,我承认我没有包括这个项目的任何代码以及我的分析过程;我选择不写它,因为我不想让这篇文章比现在更长。也就是说,请继续关注第 2 部分,我将向您展示如何制作上面概述的一些图表。
点击 此处 了解有关 Practicum 的数据分析师项目的更多信息,这是一门实践密集型课程,教授您数据科学的基础知识及其在商业分析中的应用。
另外,如果你愿意,让我们在 LinkedIn 上联系。
我的数据分析师组合。你好。我是丹尼尔,最近刚从心理学院毕业,现在是数据科学专业的毕业生…
dangarci.com](https://dangarci.com/)
数据策略:分步指南
由 Startaê Team 在 Unsplash 上拍摄的照片
如何起草计划以确保您的数据活动符合公司的战略和目标?
介绍
如果你从事数据工作,这篇文章将帮助你了解你的工作如何适应公司的整体情况。如果你拥有自己的企业,它将帮助你绘制出从数据中获取最大价值的路线图。不管怎样,理解一家公司的战略如何考虑数据比以往任何时候都更重要。
起草数据策略的主要步骤是:
- 定义你的目标
- 定义基础设施
- 定义如何培养技能
- 定义您的数据治理
- 执行和修订
让我们来详细了解一下其中的每一项。
定义你的目标
当谈到从数据中获取价值时,业务目标通常分为三类:改进业务决策、改进运营或将数据用作资产。考虑到这些类别中的一个或多个,您应该概述您的公司希望通过数据实现什么。
改善业务决策
对于您业务的每个领域,请遵循以下步骤:
- 根据你公司的目标制定战略目标
- 确定有助于实现这些目标的关键问题(每个领域约 10 个问题),然后进行优先排序
这将为您提供一个需要使用数据来回答的问题列表,它将指导您的数据策略。
改善运营
改善运营的目标分为两类:优化日常运营流程和改善客户服务:
- 优化日常运营流程
列出制造、仓储和配送、业务流程、销售和营销流程中的机会
- 改善客户服务
列出开发更好服务或更好产品的机会
将数据作为资产使用
数据作为资产的使用通常在很早以前就被定义,有时是公司自己的核心业务。然而,你也有可能通过使用你已经收集的或者已经处于有利位置的数据来创造额外的收入流。
- 提升公司整体价值
这种情况的一个很好的例子是谷歌,它自己最初的核心业务是基于聪明地使用数据。
- 通过销售数据创造额外价值
这方面的一个例子是 Fitbit,这种腕带收集用户的健康和运动相关数据,并以信息的形式出售(你的心率,你走了多少步等)。)
基础设施
在选择合适的基础架构来托管和分析您的数据时,您应该首先回答以下问题:
- 实现这些目标需要哪些数据?
- 你将使用什么资源?
记住这一点,为以下每个任务选择最佳的工具和基础设施(注意,您可以对许多甚至所有任务使用相同的工具):
- 收集
- 商店
- 分析和处理
- 访问和交流
培养技能
既然您已经定义了目标,并选择了实现目标的工具和基础架构,那么您可以更好地了解您的组织需要哪些技能来充分利用这些工具,以及如何构建它们。
可能需要的技能有以下几大类:
- 商业
- 分析的
- 计算机科学
- 统计和数学
- 创造力
在每一个领域中,您应该能够定义更具体的技能,比如编程语言或机器学习技术。使用这个框架,完成以下步骤:
- 定义你需要的技能
- 评估你已经拥有的技能
- 找到差距
- 定义填补缺口的策略(通常是这些策略的组合):
- 雇用
- 培养
- 外包
很难找到具备你所需要的所有技能的人来填补空缺,所以如果你找到具备其中一些技能的候选人,你可以考虑对他们进行培训,以建立必要的能力水平,或者将他们整合到一个互补的团队中,共同填补这些空缺。
数据治理
数据可以是一种资产,但如果你不好好保管它,它也很可能成为一种负担。您的数据应该是安全的,不会被泄露、攻击,甚至不会被组织内部不必要的人访问。
这里需要考虑几个项目:
- 在您的法律和数据部门培养必要的技能,以便您拥有精通 GDPR 和数据保护等主题的人员
- 数据最小化是一个好的实践:不要保存你不需要的数据
- 确保你有权收集和使用你需要的数据
- 确保您的流程和基础设施考虑到了安全问题
执行和修订
您最终知道了您想要实现的目标、所需的工具和技能,并确保在数据安全方面没有疏漏。现在是时候把手弄脏了:
- 将执行分成更小的块
- 建立一个时间表
- 后续行动
- 定期修改(大约一年一次,取决于你的业务需求和目标)
这些步骤可以确保您有具体的目标(例如要雇佣的人员或要采购的工具)、时间表,以及有人负责项目并确保遵守截止日期和要求。由于您的业务目标和技术环境不是静态的,这是一个迭代的过程,因此您应该定期修改您的策略以确保它保持相关性,并进行必要的更新。
结束了
这篇文章大致基于数据策略,我认为这是一本好书,但它也包含了许多可能并不适合所有人的例子。然后,我试图将关键的见解提取到一步一步的指南中,并在需要时加入我自己的意见。
数据策略:好数据与坏数据
了解好数据是什么样的,并快速发现坏数据
好数据与坏数据。好数据,从公司战略中得出数据战略,并反馈到数据决策周期中。坏数据有许多“计划”在公司里飞来飞去,却没有一致的数据策略。图片由作者提供。
1990 年,总部位于弗吉尼亚的银行“图章银行”决定信任两位聪明人,理查德·费尔班克斯和奈杰尔·莫里斯,并对数据进行重大投资。他们决定将客户信贷部门变成一个大型实验室,根据不同的信贷接受者特征“测试”出不同种类的信贷条款,并因此收集了多年的数据。
这是一项巨大的投资,该部门在相当长的一段时间里“赔钱”。但是他们真正做的是获取数据,不仅仅是因为他们认为这是一项好的投资,费尔班克斯和莫里斯收集的是好的数据。数据整合成与公司战略一致的良好数据战略。
他们收集数据的明确目的是提高 Signet Bank 信贷部门的决策能力。
好的数据也不过如此。良好的数据是您拥有良好的数据策略的结果。您收集、清理/丰富/转换、洞察这些数据,其唯一目标是改进决策。
唯一的问题是,外面有很多坏数据!无论你走到哪里,都会看到糟糕的数据。它是出于任何其他原因而接触的数据,而没有更大的目标。
我不确定仅仅在这个尺度上判断数据是否公平,但以我的经验来看,这似乎是唯一好的衡量标准。数据的唯一价值是提高公司的决策能力,从而帮助其采取更好的行动。我确实认为这是一个已经被许多伟大的思想家理解的教训。例如,Provost & Facett 在“商业数据科学:关于数据挖掘和数据分析思维,你需要知道什么”中指出了这一点。
Fairbank & Morris 明白这一点,并加以应用,将小“图章银行”变成了美国最大的银行之一 Capital One。价值十亿美元的巴西独角兽银行的联合创始人大卫·维勒兹也从一开始就直觉地理解这一点。他现在正在将他的关键见解应用到世界各地的其他市场。
现在让我们看看坏数据是什么样的,然后看一下这两个好数据的例子。
我写这篇文章是为了帮助你了解好数据和坏数据之间的巨大差异。帮助您了解在这两种情况下都可以应用的大量技术。让您有机会重新审视自己的数据策略,在竞争对手之前将坏数据转化为好数据。
坏数据
有很多坏数据的例子。当您试图让数据塑造数据策略时,坏数据就会出现,而不是相反。费尔班克斯和莫里斯没有雇佣一百名数据科学家来“想出伟大的东西”,也没有在整个公司收集大量随机数据。他们收集数据的目的是建立一个有利可图的信贷部门,然后雇佣数据科学家使用& 收集正确的数据并支持这一战略。
尽管有一些证据表明,一些科技公司实际上只是“雇佣聪明人来提出伟大的东西”,并取得了成功,但我确实认为,通常情况下,他们确实有数据战略来支持这一点。
坏数据 是…
…当一家公司投资建立一个数据湖,以“从数据中获得洞察力”,而不了解公司中是否有人会实际使用这些数据来做出比以前更好或更快的决策。
…当一家公司雇佣一些数据科学家来“利用大数据”,而没有考虑实际结果。
…当一家公司雇佣一群机器学习工程师&数据科学家来“想出一些伟大的东西”,而没有将他们融入他们的公司愿景。
…当一家公司因为中央数据收集团队出现问题而决定建立一个“数据网格”时,不知道这是否会导致其员工采取更好的行动。
…当一个分析部门因为人们要求而产生一份又一份的报告时,却不知道人们对这些报告的处理与以前有什么不同。
当一个机器学习团队花了四分之一的时间为网站构建最新的推荐引擎,而没有考虑一个简单的“热门项目”列表是否也能做得一样好。
…当一个营销部门安装了最新的营销自动化工具,但还没有大量的电子邮件营销活动和数据时。
…当一家公司花了一个季度的时间升级到最新版本的“AwesomeDataTool”时。x”到“跟随技术的步伐”,而不理解人们如何使用他们的 AwesomeDataTool 来做决定。
这不是好数据。好的数据来源于数据战略,并整合到公司战略中。
图章银行的良好数据(然后是 Capital One)
上面提到的导致坏数据的所有技术都可以用来产生好数据。也许对你来说是的。但是为了验证这一点,你必须问为什么,为什么,为什么。
要想得到好的数据,你必须从顶层开始。从数据战略开始,也就是公司战略。你必须以采取更好的行动、做出更好的决定为目标,然后沿着这条路努力。这条路是什么样子的?如今,我倾向于认为数据之路就像 ThoughtWorks 使用的流程一样:
在 ThoughtWorks Intelligence Enterprise Series,https://www . ThoughtWorks . com/insights/articles/intelligent-Enterprise-Series-models-Enterprise-Intelligence # continuous Intelligence了解更多有关此周期的信息。图片由作者提供。
因此,如果我们从目标“更好的行动”开始,那么这些阶段是:
- 采取更好的行动
- 做出采取特定行动的决定
- 具备做出具体决策的洞察力
- 拥有信息以获得洞察力。
- 已经收集了数据并将其转化为信息。
- (让动作发出数据,让它被收集……这使得圆圈变满。)
如果我们开始分析 Signet 银行案例,我们从数据策略开始。在这种情况下,它是“收集&试验信用贷款,以获得足够的信息来撇去大银行不会提供的有利可图的信贷”。
这在当时是一个非常独特的视角。上世纪 80 年代,自动违约概率计算彻底改变了信贷市场。因此,对于违约概率低的人,信贷提供保持在标准利率。但在 1990 年,费尔班克斯和莫里斯决定,是时候对两者都下注了,价格歧视,或者换句话说,为不同的人提供不同的信用条款,关注盈利能力,而不仅仅是违约概率。
所以从本质上说,他们认为在违约概率较高的人群中,仍然存在有利可图的贷款!在违约概率较低的人群中,有更多的盈利选择,因为这些人实际上是信贷部门的输家。
因此,我们可以推导出 Signet Bank 正在采取并希望改进的行动…
图片由作者提供。
行动——“提供更有利可图的贷款”。正如费尔班克斯和莫瑞斯指出的那样,他们需要同时关注盈利能力和违约概率。这个有争议的决定就变成了…
决策—“我向什么样的客户提供什么样的贷款?”。在 20 世纪 80 年代,这一决定仅仅是基于这样一种认识,即有些人违约概率较高,而有些人违约概率较低。图章银行现在寻求的是一种不同的洞察力…
洞察——“我应该向哪些特定的客户提供哪些特定类型的贷款条件?”。为了获得这些见解,我们需要更多的信息。不幸的是,这些信息在 1990 年根本不存在。提供的唯一信息是基于社会人口数据的违约概率。
信息——“关于不同客户群盈利能力的硬数据,以及传统的违约概率和关于其他银行服务的客户类型、他们提供的条件的数据!!"。因为所有这些信息都会影响到向谁提供什么产品包的决策。所以最后归结到数据的问题,原始数据根本不可用…
数据—“新数据,提供给具有不同社会人口背景的人的不同条件的实验数据,以及他们各自的盈利能力和违约概率”。因此,对 Signet Bank 来说,事实证明,好的数据策略是收集这种数据,将其与其他数据源结合起来,再次回到循环中,进而做出更好的决策。
当然,之后,这个轮子继续转动,为每个阶段增加更多的价值。据报道,Capital One 每年都会推出数千种此类变体。要更深入地了解这个案例,请参阅 Provost 和 Fawcett 在 2013 年发表的“商业数据科学:你需要了解的数据挖掘和数据分析思维”。
这种情况已经很久了,公司倾向于不共享这种数据。但我最近无意中发现了金融业的另一个相关案例,即巴西独角兽企业“Nubank”。
nu bank 的良好数据
我知道 Nubank 是因为他们的机器学习框架 fklearn 。该公司正在机器学习问题领域创造大量开源软件。但事实证明,Nubank 不只是投资于机器学习,因为行业内的其他人都这样做,对机器学习和算法的投资与公司战略和由此产生的数据战略密切相关。
Nubank 正在打入“无银行市场”,这个市场在巴西约占人口的 50%。这些人根本无法进入银行系统。Nubank 正在通过提供针对特定群体的产品来改变这种状况。第一款产品是一款仅支持手机的托管信用卡,旨在以巴西金融业前所未有的速度向没有银行账户的人提供小额贷款,更多的产品正沿着这条道路前进。
但是,为“无银行账户者”服务带来了一个显而易见的问题:你如何知道一个无银行账户者是否会违约,或者对公司来说是否有利可图?毕竟,他们没有银行历史或金融信息,如信用评分。
这就是数据策略变得明显的地方。Nubank 的创始人大卫·维勒兹在一定程度上描述了他们是如何准确地意识到这个问题的,以及他们必须专注于非传统的方式来查看和收集他们的数据。
**让我们从头开始:**为没有银行账户的人提供合理收费的信用卡,同时保持盈利。这就是我们想要采取的行动。因此,数据策略变成了“收集&分析数据,以得出哪些人提供哪种产品,是信用卡、贷款还是借记卡”…
图片由作者提供。
行动—“应该向哪个客户提供哪个产品?用什么样的信用额度?以保持盈利。”。毕竟,就像 Signet Bank 一样,Nubank 正在应对巴西传统银行系统回避的一批客户。关键决策是像…这样的决策。
决策—“我们可以向哪个特定客户提供万事达信用卡,我们可以向谁提供贷款,贷款利率是多少?”。为了做出这样的决定,Nubank 需要…
洞察&信息—“客户的盈利能力以及违约概率。但作为一家快速增长的初创企业,他们也需要人们向其他人推荐该银行的可能性,就像忠诚度计划等其他产品的可能用途一样。”。
根据维勒兹在 CNN 采访中的说法,这正是 Nubank 正在建立并继续发展的:
“然而,Nubank 已经将其业务建立在一个全新的基础上:基于“大量非传统信息”的独特数据集和算法,”贝莱斯说
还有…
“我们关注你住在哪里……你如何移动,你的朋友是谁,谁邀请你去 Nubank,你向什么类型的人寄钱,”他说。“我们观察你是否阅读信用卡的合同——事实证明,真正快速阅读合同的人往往是骗子。我们会观察你正在进行的交易类型,你是在购买食品杂货,还是在酒吧里。”
事实上,如果你仔细观察,Nubank 在利用这类信息方面处于特别有利的位置,因为他们也收集了许多传统公司不收集的数据!他们已经在你的手机上,有一个推荐程序,从而收集关键的行为数据。
事实上,这一点得到了 2011 年 Martens&Provost 的一项研究的支持,该研究基本上说:对于银行来说,使用你住在哪里和你的年龄的信息来计算&是一个很好的起点,但是使用 行为数据 就像这里提到的那些提供了盈利能力的实质性提升,并且增加了使用的数据越多,而社会人口统计数据则达到了一个明确的顶点。
您还应该注意到,Nubank 处于独特的位置,可以进行与 Signet Bank 非常相似的实验!他们目前提供了巴西 50%的新发行信用卡,这意味着他们控制了无银行市场的巨大份额。通过对无银行账户市场进行实验,他们可以根据目前收集的数据点得出非常相似的见解。他们比任何人都做得更好。
这是否足以保持盈利还有待观察,但至少足以让 Nubank 成为一家价值 10 亿美元的初创企业,并让它们在全球扩张。
如果你想让你的公司在使用数据方面有所作为,我希望这能帮助你辨别好数据和坏数据,并让你走上正确的道路,专注于数据战略,而不是让数据决定“战略”。敬请关注。还有更多的好数据即将出炉。
资源
- F.Provost,T. Fawcett,2013:“商业数据科学:你需要了解的数据挖掘和数据分析思维”。Provost 和 Fawcett 提供并分析了 Signet 银行(现在的 Capital One)的案例。他们还提出了数据科学是关于提高公司决策能力的观点。
- J.Pepitone,2019: CNN,拉丁美洲最有价值的初创公司之一正在改变巴西银行的方式)。
- R.Rumelt,好策略坏策略,2011: 这本书启发了本文的某些部分。
- Fklearn ,Nubank 打造的开源功能性机器学习框架。
- D.Martens,F. Provost,2011: 从消费者交易数据中锁定伪社交网络
- ThoughtWorks 智能企业系列。
数据结构 1:哈希表
哈希表及相关概念的简要介绍
哈希表是实现字典的有效方法。在直接进入哈希表主题之前,掌握背景知识将有助于我们更好地理解与哈希表相关的概念。
什么是字典?
字典是一种抽象数据类型,用于存储键值对。
一个简单的字典→ {key_1:value_1,key_2:value_2,…。,key_n:value_n}
每个键都与一个值相关联。让我们用一个简单的例子来构建我们的整个讨论。
假设您是一家销售各种水果和蔬菜的商店的老板,您想创建一个包含商店中所有产品及其相应价格的列表。为了简化这个过程,您决定为每个产品创建一个唯一的 productId。
这里要注意的一个重要事实是,第 1 列包含了人类已知的所有水果和蔬菜的名称,现在让我们假设你正在销售所有这些品种。第二列是一个万能设置 U 的按键对应每个产品。下一步是创建一个数据结构来存储这些细节(即键值对)
直接地址表
存储键(productIds)及其对应值(prices)的最简单方法之一是使用直接地址表。直接地址表只不过是一个数组,其中每个位置/槽对应于通用集合 U 中的一个键。下图说明了如何使用直接地址表来存储键值对。
作者图片
set U 中的每个元素都被映射到数组 T 中对应的槽中(为简单起见,图中只映射了键 0、1 和 2)。这里,pᵢ是 key i. 对应的价格,从图中可以看出,数组 T 中的 slot k 对应的是 universal set U 中的 key k ,*t【k】*是 key k. 对应的值
任何数据结构都支持三个主要功能:插入、删除和搜索。
INSERT(T, key, value):
T[key] = valueDELETE(T, key):
T[key] = NILSEARCH (T, key):
return T[key]
所有三个操作都有 O(1) 的最坏情况时间复杂度,这是我们能达到的最好的时间复杂度。
直接访问表的限制
让我们继续我们的店主场景。
限制 1:
过了一段时间,你发现你的商店亏本了,于是你决定只卖有限种类的水果和蔬菜。假设你打算只卖 m (m < n) 种水果和蔬菜,并且每周都要更换品种。现在,你的直接访问表应该是这样的。
作者图片
黄色区域包含与您选择的 m 种水果和蔬菜相对应的 m 个按键。现在,只有与黄色区域中的键相对应的槽具有值,其余的为零,即仅使用了 n 槽中的 m 槽,我们正在浪费( m-n 槽所占用的内存。假设 n = 100,000,m=1000,那么您将浪费为该阵列分配的 99%的内存。因此,当通用集合 U 的大小与实际使用的键的数量相比非常大时,直接访问表在存储器使用方面变得低效。
限制 2:
如果您有 2Gb 的 RAM,并且使用 8 个字节来存储每个值(价格)会怎么样?
数组的最大大小 T = (2 * 1024 * 1024 * 1024) / 8 = 268,435,456
如果通用集合 U 的大小大于 268,435,456,则直接访问表无法使用,因为没有足够的内存来创建数组。
因此,直接访问表只对合理的大小有效 n.
哈希表
哈希表克服了上述两个限制。但是哈希表是如何工作的呢???
通过直接寻址,一个键为 k 的元素被存储在槽 k 中。在散列表中,具有键 k 的元素被存储在槽*h(k)*中,其中 h 被称为散列函数。下图将清楚地说明键是如何映射的。
作者图片
哈希函数将通用集合 U 中的每个键映射到数组 T 中的一个槽。如果数组 T 的大小是 m ,那么
h : U → {0,1,2,…,m-1}
之前对于直接访问表,数组 T 的大小应该是| U |(数组 U 的大小)。然而,哈希表允许数组的大小为m(m<|U|)也就是说,我们可以创建任意大小的数组,而不考虑通用集合 U 的大小。
即使哈希表克服了直接地址表的限制,哈希表也有自己的挑战。
冲突
如果两个不同的键 k₁ 和 k₂ 有相同的哈希值,即 h(k₁) = h(k₂,会发生什么?这种现象叫做碰撞。还好有办法处理。
- 选择一个有效的散列函数来有效地分配密钥将减少发生冲突的可能性。让我们将黄色区域中的一组实际键命名为 set W 。如果集合 W 的大小大于数组 T 的大小(即|W|>|T|),那么冲突就无法完全避免。然而,通过使用均匀分布密钥的有效散列函数,冲突的数量可以被最小化。
- 链接是用于冲突解决的另一种方法。假设两个键 k₁ 和 k₄ 有相同的哈希值,那么我们使用一个链表(通常是双向链表)来存储它们,如下图所示。
作者图片
链式哈希表时间复杂度
- 插入
键值对被插入到链表的头部。如果我们假设被插入的键还不存在,那么最坏情况下插入的运行时间是 O(1)。我们可以在插入之前检查一个键是否已经存在,这需要额外的成本(这与链表的大小成正比,因为您必须遍历链表)
- 删除
假设链表是一个双向链表,双向链表中的一个元素 x (键值对/链表中的一个节点)可以以最坏的时间复杂度 O(1)被删除(更多关于双向链表删除的信息)。然而,如果 delete 函数的输入是 key k (不是节点 x ),那么删除的时间复杂度与链表的大小成比例,因为您必须遍历链表来找到相应的节点。
- 搜索
对于 search 来说,最差情况下的运行时间是 O(| W |),因为在最差情况下,散列函数会为所有键产生相同的散列值。然而,在实践中,这种情况从未发生过。理论上已经证明,对于存储 n 个元素的 m 大小的哈希表(定义 n / m 为负载因子),在简单均匀哈希的假设下,平均用例运行时间为θ(1+n/m)。
什么是好的哈希函数?
在一个好的散列函数中,每个键都同样可能散列到数组 T 中的 m 个槽中的任何一个,而与任何其他键散列到的位置无关。然而,重要的是要注意,我们不能检查这个属性,因为我们很少事先知道,从其中提取密钥的概率分布。
本文只打算提供哈希表的一个高层次的整体概念(不是非常高级的概念)。如果你感兴趣的话,可以参考下面这本书,这本书是我写这篇文章的主要参考资料。
资源: 科尔曼、T. H .、莱瑟森、C. E .、里维斯特、R. L .、&斯坦恩、C. (2009)。算法介绍。麻省理工出版社。
数据结构 2:跳过列表
平衡树的概率替代方案
介绍
二叉树可以用来表示抽象数据类型,如字典和有序列表。然而,当我们按顺序插入元素时,很有可能最终得到一个性能很差的退化数据结构(例如,按顺序向二叉树插入 2,3,4,5,6,7,8)。这就是为什么我们经常使用平衡的树形结构,在执行操作时重新排列树形结构。跳过列表可以作为平衡树的替代物(根据约束条件,它们甚至可以执行得很好)。由于它们不像平衡树结构那样流行,我认为作为我的数据结构系列的第二篇文章来写这篇文章可能会有用。
链接列表
链表是一种非常熟悉的数据结构,简单且易于实现。下图说明了一个排序的单链表,其中每个节点都包含一个指向其右邻居的指针。
作者图片
排序链表操作及时间复杂度
我们寻找的常见操作是搜索、插入和删除。让我们来看看伪代码。
Search (list,search key)
//通过线性搜索找到 search keyInsert(node,newNode)//插入 new node 作为节点
new node . next←node . next
node . next←new nodeDelete(node)//删除节点
obsoleteNode←node . next
node . next←node . next . next
free(obsolete
相应地,平均案件时间复杂性将是,
-搜索:θ(n)
-插入:θ(1)
-删除:θ(1)
其中 n 是链表的长度(或链表中的节点数)
缺点
- 搜索效率不高。很难在不到 O(n)的时间内进行搜索
- 遍历很慢,因为您必须一次遍历一个节点,从第一个节点开始到感兴趣的节点。不能直接跳到中间。
跳过列表提供了这些缺点的解决方案。
完美的跳过列表
让我们对链表做一些修改。在我们最初的链表中,每个节点只有一个指向它右边邻居的指针。对于每隔一个节点,让我们添加一个指针指向前面两个节点。下图给出了一个更好的想法。
作者图片
对于一个链表,我们必须在搜索操作中遍历 n 个节点(在最坏的情况下)。现在,经过我们的修改,我们只需要遍历 n /2 + 1 个节点。例如,如果我们必须搜索值 80,
作者图片
我们从最高级别的标题开始。它指向值为 10 的节点。80 大于 10,所以我们移动到值为 10 的节点。它指向 28,也小于 80。我们继续这样,直到我们达到 70 岁。它指向值为 90 的节点。因为 90 大于 80,所以我们要低一级。现在指针指向 80。任务完成!!!我们只遍历了 4 个节点,而不是 7 个(这是典型链表的情况)。
让我们更进一步,为每第四个节点添加一个指针,指向前面第四个节点。下图演示了新的结构。
作者图片
现在搜索怎么样了?我们只需要遍历 n /4 + 2 个节点。如果我们搜索 80 个,
作者图片
我们可以这样继续下去,每一个 2ˣ)th 节点前面都有一个指针指向 2ˣ节点。我们可以很容易地观察到新数据结构中的一些重要特性,我们还没有命名这些特性。
- 将会有最大的 log₂(n)等级(确切地说是 ceil(log₂(n)
【在我们的例子中,log₂(8) = 3 个等级】 - 每个更高的级别将包含其下一级别的一半数量的元素。[在我们的示例中,级别 1 有 8 个元素/节点,级别 2 有 4 个元素,级别 3 只有 2 个元素]
- header 和 NIL(姑且这样称呼吧)节点在每一层中都存在
- 它非常类似于平衡二叉树
现在我们可以在 O(log n )时间内搜索,即使是在最坏的情况下,这与链表相比是一个显著的改进。这就是我们所说的完美跳过列表(最后我们给我们的新数据结构起了一个名字😊)**。**即使搜索性能很好,插入和删除现在也很困难,因为很难保持这种模式,因为新节点可以插入到任何地方,或者任何现有节点都可能被删除。在每次插入/删除之后,我们不能重构整个列表。我们该如何处理?通过 随机化 。
随机跳过列表
让我们放松一点约束。之前我们说过,每一级的元素数量应该是下一级的一半。现在让我们期待每一层的元素数量是其下一层的一半。
让我们假设我有一枚硬币。当我插入一个新的节点时,我会投掷硬币(任意次)直到我得到一条尾巴。我获得的连续人头数将决定我应该给那个特定的节点添加多少层。
插入操作
下图清楚地说明了如何在列表中插入 35。
作者图片
步骤 1: 插入 35,创建一个指向右边邻居的指针。
**第二步:**抛硬币。
**第三步:**由于是人头,增加一级,连接到右边最近的节点,至少 2 级。连接左侧最近的节点至少 2 级
**第四步:**再抛硬币。
**第五步:**由于是尾数,终止。
假设如果我们在得到尾部之前得到了另一个头部,那么我们当前的节点将有三个级别,而 header 和 NIL 将只有两个级别。在这种情况下,我们应该向 header 和 NIL 节点添加额外的级别。header 和 NIL 节点应该始终具有与具有最大级数的节点相同的级数。
删除操作
现在就删 70 吧。
作者图片
我相信图表本身解释了一切。
搜索操作的方式与完全跳过列表中的方式完全相同。
掷硬币事件是引入随机化的事件。因为得到正面的概率是 0.5,所以我们试图重新创建一个随机化的跳过列表,它类似于我们的完美跳过列表,其中每一级与下一级相比只有一半的元素数量。
如果我们使用不同的概率函数,时间和内存消耗以及跳表结构会发生什么是一个有趣的话题。
【你可以从底部提供的资源中了解更多相关信息】
值得注意的是,我们牺牲内存来提高性能。添加额外的级别意味着添加更多的指针(在 64 位系统上,每个指针消耗 8 个字节)。
结论
- 跳跃列表可用作平衡树(如 AVL 树)和自调整树(如八字树)的替代物。然而,跳转列表是一个相对简单的数据结构,非常容易实现。
- 我们可以很容易地修改跳转列表来引入并行性(甚至有并行无锁跳转列表)
- 为了减少时间消耗,跳转列表倾向于使用稍微多一点的内存(在任何数据结构中,内存和性能之间的权衡是不可避免的)
最后一个重要问题。为什么我们称之为跳过列表?因为我们在遍历过程中跳过了节点(如果你到现在还没弄明白的话😂)
资源:普格,W. (1990)。跳过列表:平衡树的概率替代。ACM 的通信, 33 (6),668–676。
Python 中的数据结构和算法
开始掌握数据结构和算法概述
为了发展一个完整的头脑:研究艺术的科学;研究科学艺术。学会如何去看。意识到每件事都与其他事相关联。
列奥纳多·达· 芬奇
介绍
这篇文章的目的是给你一个 Python 中数据结构和算法的全景。对于数据科学家来说,这个主题非常重要,可以帮助他或她以更有效的方式设计和解决机器学习模型。
我们将看到内置数据结构、用户定义数据结构的实际例子,最后但同样重要的是,我将向您介绍一些算法,如遍历算法、排序算法和搜索算法。
所以,让我们开始吧!
第一部分:内置数据结构
顾名思义,数据结构允许我们组织、存储和管理数据,以便有效地访问和修改。
在这一部分,我们将看看内置的数据结构。Python 中有四种类型的内置数据结构:列表、元组、集合和字典。
列表
列表是用方括号定义的,包含用逗号分隔的数据。该列表是可变的和有序的。它可以包含不同数据类型的混合。
出局:
january['january', 'february', 'march', 'april', 'may', 'june', 'july']['birthday', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
下面是列表中一些有用的函数。
出局:
What
is
your
favourite
painting
?Who-is-your-favourite-artist-?
出局:
['Chagall', 'Kandinskij', 'Dalí', 'da Vinci', 'Picasso', 'Warhol', 'Basquiat']
元组
元组是另一种容器。它是不可变有序元素序列的数据类型。不可变的,因为你不能从元组中添加和删除元素,或者就地排序。
出局:
The dimensions are 7 x 3 x 1
设置
集合是唯一元素的可变且无序的集合。它可以让我们快速地从列表中删除重复项。
出局:
{1, 2, 3, 5, 6}
False
Basquiat
字典
字典是一种可变的无序的数据结构。它允许存储一对项(即键和值)。
如下例所示,在字典中,可以将容器包含到其他容器中以创建复合数据结构。
出局:
In a Sentimental Mood
Lacrimosa
第二部分:用户定义的数据结构
现在我将向您介绍三种用户定义的数据结构:ques、stack 和 tree。我假设您对类和函数有基本的了解。
堆栈使用数组
堆栈是一种线性数据结构,其中元素按顺序排列。它遵循 L.I.F.O 的机制,意思是后进先出。因此,最后插入的元素将作为第一个元素被删除。这些操作是:
- Push →将元素插入堆栈
- Pop →从堆栈中删除元素
要检查的条件:
- 溢出情况→当我们试图将一个元素放入已经具有最大元素数的堆栈时,就会出现这种情况。
- 下溢情况→当我们试图从空堆栈中删除一个元素时,就会出现这种情况。
出局:
5
True
[10, 23, 25, 27, 11]
overflow
11
27
25
23
10
underflow
队列使用数组
队列是一种线性数据结构,其中的元素按顺序排列。它遵循先进先出的 F.I.F.O 机制。想想当你和你的朋友去电影院时,你可以想象第一个给你票的人也是第一个站出来的人。队列的机制是相同的。
描述队列特征的方面。
两端:
- 正面→指向起始元素
- 后→指向最后一个元素
有两种操作:
- 入队→将元素插入队列。它将在后方完成。
- 出列→从队列中删除元素。这将在前线完成。
有两个条件:
- 溢出→插入到已满的队列中
- 下溢→从空队列中删除
出局:
[2, 3, 4, 5]
[3, 4, 5]
树(一般树)树
树用于定义层次结构。它从根节点开始,再往下,最后的节点称为子节点。
在本文中,我主要关注二叉树。二叉树是一种树形数据结构,其中每个节点最多有两个孩子,称为左孩子和右孩子。下面你可以看到一个用 python 编写的二叉树的表示和例子,其中我构建了一个名为 Node 的类和表示不同节点(A、B、C、D 和 E)的对象。
作者图片
无论如何,还有其他用户定义的数据结构,如链表和图。
第三部分:算法
算法的概念自古就有。事实上,古埃及人使用算法来解决他们的问题。然后他们把这种方法传授给希腊人。
algorithm 一词源自 9 世纪波斯数学家 muḥammad·伊本·穆斯哈勒·赫瓦·里兹姆,他的名字拉丁化为 Algorithmi。Al-Khwārizm 也是一名天文学家、地理学家和巴格达智慧之家的学者。
正如你已经知道的,算法是以有限的顺序来解决问题的指令。
当我们编写一个算法时,我们必须知道确切的问题是什么,确定我们需要从哪里开始和停止,并制定中间步骤。
有三种解决算法的主要方法:
- Divide et Impera(也称为分而治之)→它将问题分成子部分,并分别解决每个子部分
- 动态规划→它将问题分成子部分,记住子部分的结果,并将其应用于类似的问题
- 贪婪算法→解决问题时采取最简单的步骤,而不用担心未来步骤的复杂性
树遍历算法
python 中的树是非线性数据结构。它们以根和节点为特征。我用之前为二叉树构造的类。
树遍历指的是恰好访问树中的每个节点一次,以便更新或检查它们。
作者图片
有三种类型的树遍历:
- 有序遍历→指的是先访问左边的节点,然后是根,再是右边的节点。
这里 D 是最左边的节点,其中最近的根是 B .根 B 的右边是 e .现在左边的子树完成了,所以我向根节点 A 移动,然后到节点 c。
出局:
D
B
E
A
C
- 前序遍历→指的是先访问根节点,然后是左节点,再是右节点。
在这种情况下,我移动到根节点 A,然后移动到左边的子节点 B 和子节点 d。之后,我可以转到节点 E,然后是节点 c。
出局:
A
B
D
E
C
- 后序遍历→指的是先访问左边的节点,然后是右边的节点,最后是根节点
我到最左边的节点 D,然后到右边的节点 e,然后,我可以从左边的节点 B 到右边的节点 c,最后,我向根节点 a 移动。
出局:
D
E
B
C
A
排序算法
排序算法用于按照给定的顺序对数据进行排序。可分为归并排序和冒泡排序。
- 归并排序→遵循分治规则。给定的列表首先被划分成更小的列表,并比较相邻的列表,然后按照所需的顺序对它们进行重新排序。因此,从作为输入的无序元素总结,我们需要有作为输出的有序元素。下面是描述每个步骤的代码。
出局:
input - unordered elements: 15 1 19 93
output - ordered elements:
[1, 15, 19, 93]
- 冒泡排序→它首先比较相邻的元素,如果它们没有按照指定的顺序排序,则进行排序。
出局:
[1, 3, 9, 15]
- 插入排序→它在给定的列表中选择一个条目,并把它放在应该放的地方。
出局:
[1, 3, 9, 15]
还有其他排序算法像选择排序和外壳排序。
搜索算法
搜索算法用于寻找给定数据集中存在的一些元素。有许多类型的搜索算法,如线性搜索,二分搜索法,指数搜索,插值搜索,等等。在这一节,我们将看到线性搜索和二分搜索法。
- 线性搜索→在一维数组中,我们必须搜索特定的关键元素。输入是我们想要找到的一组元素和关键元素。因此,我们必须将关键元素与组中的每个元素进行比较。在下面的代码中,我尝试在列表中查找元素 27。
出局:
'not fund'
- 二分搜索法→在这个算法中,我们假设列表是升序排列的。因此,如果搜索关键字的值小于列表中间的元素,我们将区间缩小到下半部分。否则,我们缩小到上半部分。我们继续检查,直到找到值或者列表为空。
出局:
False
True
结论
现在你对数据结构和算法有了一个大概的了解。所以,你可以开始深入理解算法。
我为这篇文章选择的维特鲁威人的美丽形象不是随便的。这幅画是基于理想人体与几何学的关系。事实上,对于这种表现,达芬奇受到了维特鲁威斯的启发,他将人的身体描述为确定建筑中正确比例的理想身体。
关于算法,维特鲁威人隐藏了一个秘密算法,这个算法被艺术家们使用了几个世纪,以证明他们的作品受到了神圣比例的启发。
有时候我喜欢想,也许达芬奇,通过他的精彩作品,想要定义最重要的算法,也就是生命的算法。
感谢你阅读这篇文章。您还可以通过其他方式与我保持联系并关注我的工作:
数据结构和算法之旅
我们将开始一系列基于数据结构和算法的课程。
作者图片
本文目标:
- 描述数据结构的重要性
- 描述术语算法
- 什么是算法复杂性
- 求解算法的方法
他的特定概念被认为是软件工程中最重要的概念之一,并成为大多数顶级公司的主要检查点。在这一系列课程中,我们将讨论数据结构和算法概念背后的思想,并在接下来的课程中实现几种算法。
什么是数据结构?
数据结构的简单定义是“在你的计算机上存储数据的不同方式”或者“表示和组织你的数据的系统方式”。重要的是,任何数据结构都应该能够有效地用于任何给定的任务。例如,搜索、转换数据、编辑、更新等。
数据结构的特征
数据结构有三种不同的特性,我们可以根据用途对它们进行分类。
- 时间复杂度
运行时间或 执行时间 被定义为给定任务的时间复杂度。对于给定的上下文,我们应该使用尽可能好的数据结构,以尽可能降低时间复杂度。
- 基于特定接口的正确性
每个数据结构都包含其 接口 即给定数据结构所支持的操作。同样,应该有一个正确的 实现基于正确接口的 数据结构。理想情况下,数据结构应该带有正确定义的接口和描述性实现。
- 空间复杂度
空间复杂度将衡量给定数据结构的内存使用情况。最终,我们应该优化我们的算法解决方案,以便对于具有大量数据集的解决方案,尽可能地最小化空间复杂度。
为什么我们需要任何类型的数据结构?
如今,随着新处理器、计算机系统的发展,用我们普通的计算机处理大量的数据记录并不是一项复杂或详尽的任务。但是,当遇到基于一些标准的某些不可预测的情况时,如数据大小、检索速度和多线程处理,我们应该专注于为给定场景实现适当的数据结构。
假设您正在实现一个简单的文本搜索,该搜索基于一个拥有超过数百万条记录的大型文本语料库。如果您试图并行处理数据对象,并且您的执行时间不应该超过亚毫秒。
正确设计的数据结构将帮助您以高效的方式完成这些类型的任务。
什么是算法?
算法是完成任何任务的一步一步的过程。换句话说,算法是一组定义明确的指令,用来完成给定的任务,而不依赖于任何特定的编程语言。在本系列中,我们将尝试用 nodejs 和 python 编程语言实现主要的数据结构和算法,以了解任何给定算法的相似性。
给定算法的属性
正如我们之前提到的,算法应该有一组定义良好的指令来完成给定的任务,即使你有一组指令来完成一项任务,但如果以下特征不满足,则该任务将不会被定义为算法。
算法属性(图片由作者提供)
- 毫不含糊
算法的每一步都应该清晰,所有的输入输出也应该清晰。
- 有限性
算法应该能够在有限数量的步骤出现后终止
- 可行性
该算法应该能够利用可用资源工作
- 独立
算法中的所有步骤应与语言无关(应能在任何编程语言中实现)
- 输入
算法应该有零个或多个明确的输入,这些输入应该是定义明确的输入。
- 输出
算法应产生一个或多个明确定义的输出。
问题可以用很多方法解决。让我们设计一个简单的算法来识别和描述我们在上一段中解释的过程。
**例子:**设计一个算法,生成给定数字的平方值。
**Step 1 −** START**Step 2** − define a value **A** which you need to get the square**Step 3** − define the operation of **A** * **A****Step 4** − store output of step 3 to new variable **C****Step 5** − return **C****Step 6** − STOP
现在,仔细检查一个算法的所有特征,并尝试对照上面的简单算法来证明所有特征的合理性。
**自学:**尝试设计以下算法,检查你的算法是否支持我们之前描述的所有属性。
- 设计一个算法,从给定的三个正数中确定最大值
- 设计一个算法来得到斐波那契数列的第 10 个值
在分析你的算法时,算法的理论分析是非常重要的。 先验分析是一种着重分析算法效率的分析类型。
一个算法的效率可以用算法复杂度来衡量。算法的复杂度主要基于两个因素,即时间因素和空间因素。
空间复杂性
空间复杂度由算法执行其生命周期时所需的内存空间总量来衡量。
**Total Space Complexity = "**fix part complexity**" + "**variable part complexity**"**
固定零件复杂度被认为是存储相关数据和变量所需的空间量。例如,指定的常数,独立于问题大小的变量。
可变零件复杂性取决于问题的大小,并且组件在执行时间内动态变化。
时间复杂度
时间复杂度是指完成给定算法所需的时间单位总量。
时间复杂度与输入大小线性相关,这意味着当输入大小增加时,时间复杂度也随之增加。
渐近分析
在算法世界中,计算给定算法的运行时间指的是渐近分析。
算法的运行时间总是基于参数和算法背后运行的函数。
通常我们可以基于三种不同的情况来测量算法运行时间(最佳、平均和最差)。
我们主要考虑最坏情况的场景,这意味着执行算法所需的最大时间。
流行算法的渐近表示法
复杂性计算(图片由作者提供)
算法的设计是为了获得所需问题的最优解。在寻找解决方案的过程中,我们可以定义三种不同的方法来解决特定的问题。
实现算法最优解的方法(图片由作者提供)
贪婪的方法
贪婪方法是一种常见的问题解决方法,它使用该方法通过寻找局部最优解来解决特定问题。
大多数时候贪婪方法收敛于局部最优解,而很难得到全局最优解中的,但是随着时间和迭代,局部最优解接近全局最优解。
例:在下面的树形结构中找到和最大的路径
贪婪方法与实际解决方案(图片由作者提供)
以下是一些流行的贪婪算法示例:
- 旅行推销员问题
- 克鲁斯卡尔最小生成树算法
- Dijkstra 的最小生成树算法
- 背包问题
- 作业调度问题
尝试使用适当的算法解决这个问题。
分而治之
分而治之很好理解。在这种方法中,问题将分成更小的子问题,这些子问题将独立解决。当你把主问题分成子问题时,你可以一直分下去,直到你找到一个原子问题(不能再分成更多部分的子问题)。一旦你单独解决了子问题,你就可以合并所有的结果来生成你的问题的最优解。
示例:查找一组给定数据库记录的最长公共前缀
实际应用中的分而治之(作者图片)
以下是一些流行的贪婪算法示例:
- 二分搜索法算法
- 合并排序算法
- 快速排序算法
- 最近点对法
- 库利-土耳其快速傅立叶变换(FFT)
动态编程方法
动态编程方法非常类似于分而治之的方法,但是由于问题解决方法的原因,它与分而治之的方法略有不同。
不像分而治之的方法,一旦你把你的问题分成亚原子层次的问题,你就不能单独解决那些原子层次的问题。不是解决单个原子级的问题,你将被记住,并在其他重叠的(重复的)子问题中使用那些更小的原子级问题的结果。
在动态编程方法中,通过使用原子问题的最优解和记忆机制可以获得最优解。
**备忘录:**动态编程使用先前解决的亚原子问题的记忆结果来解决给定迭代中类似的小问题。
示例 : 斐波那契数列数值表示
动态编程在斐波那契数列中的应用(图片由作者提供)
以下是一些流行的贪婪算法示例:
- 背包问题
- 项目调度问题
- Dijkstra 算法的最短路径
- 汉诺塔问题
- 斐波那契数列
总之,我们已经了解了各种数据结构和算法的基本定义和思想。在以后的文章中,我们将讨论几种算法表示和许多现实问题的解决方案。除此之外,我们将实现算法来解决上述问题。
C++中的数据结构—第 1 部分
在 C++中实现通用数据结构
C++是支持创建类的 C 编程语言的扩展,因此被称为“ C with classes ”。它用于创建高性能应用程序,并为我们提供对计算资源的高级控制。在本文中,我将向您展示我在上一篇文章每个程序员都必须知道的 8 种常见数据结构中讨论的 4 种数据结构的 C++实现。
数据结构是一种在计算机中组织和存储数据的专门方法,以这种方式我们可以执行…
towardsdatascience.com](/8-common-data-structures-every-programmer-must-know-171acf6a1a42)
让我们深入研究代码。
1.数组
一个数组是一个固定大小的结构,可以保存相同数据类型的项目。数组是有索引的,这意味着随机访问是可能的。在许多编程语言中,数组通常以本机数据结构的形式出现。然而,不要把array
和像 python 这样的语言中的list
这样的数据结构混淆。让我们看看数组是用 C++表示的;
// simple declaration
int array[] = {1, 2, 3, 4, 5 };
// in pointer form (refers to an object stored in heap)
int * array = new int[5];
然而,我们已经习惯了更友好的vector<T>
数据结构,我们可以将数据推送到这种结构,而不用担心数据的大小。让我们看看如何实现一个我们自己的list
数据结构,它可以自己调整大小。
using namespace std;
class DynamicArray
{
private:
int size_;
int max_;
int *arrayholder_;
public:
DynamicArray()
{
this->size_ = 0;
this->max_ = 5;
this->arrayholder_ = new int[5];
}
~DynamicArray()
{
delete[] this->arrayholder_;
}
int size()
{
return this->size_;
}
int& operator[](int i)
{
assert(i < this->size_);
return this->arrayholder_[i];
}
void add(int n)
{
if (this->max_ < this->size_ + 1)
{
this->max_ *= 2;
int *tmp_ = new int[this->max_];
for (size_t i = 0; i < this->size_; i++)
{
tmp_[i] = this->arrayholder_[i];
}
delete[] this->arrayholder_;
this->arrayholder_ = tmp_;
this->arrayholder_[this->size_] = n;
this->size_ += 1;
}
else
{
this->arrayholder_[this->size_] = n;
this->size_ += 1;
}
}
};
int main(int argc, char **argv)
{
DynamicArray darray;
vector<int> varray;
for (size_t i = 0; i <= 15; i++)
{
darray.add(i);
}
return 0;
}
我们可以看到上面的DynamicArray
的初始大小为 5。同样在add
函数中,我们可以看到,如果我们已经达到了最大容量,数组大小将被加倍和复制。这就是vector<T>
数据结构在现实中的工作方式。通常,基数在 30 左右。知道这一点可能会在面试中有所帮助。
还有一点需要注意的是,我们同时使用了构造函数和析构函数。这是因为我们有一个指针指向随着数组扩展而分配的内存。必须释放这些分配的内存以避免溢出。这个实现的美妙之处在于用户不需要知道任何关于丑陋指针的事情。重载operator[]
允许像本地数组一样进行索引访问。有了这些知识,让我们继续下一个数据结构,链表。
2.链接列表
链表是一个顺序结构,由一系列相互链接的线性项目组成。您只需要知道遍历这个数据结构的链的一端。在大小频繁变化的情况下,使用数组可能没有优势,除非随机访问数据(除非实现收缩操作,否则扩展会导致更长的复制时间并使用更多内存)。因此,链表可以被认为是一种支持频繁大小变化和顺序访问的数据结构。
让我们看一下我们的实现。
using namespace std;
template <typename T>
class Node
{
public:
T value;
Node *next;
Node *previous;
Node(T value)
{
this->value = value;
}
};
template <typename T>
class LinkedList
{
private:
int size_;
Node<T> *head_ = NULL;
Node<T> *tail_ = NULL;
Node<T> *itr_ = NULL;
public:
LinkedList()
{
this->size_ = 0;
}
void append(T value)
{
if (this->head_ == NULL)
{
this->head_ = new Node<T>(value);
this->tail_ = this->head_;
}
else
{
this->tail_->next = new Node<T>(value);
this->tail_->next->previous = this->tail_;
this->tail_ = this->tail_->next;
}
this->size_ += 1;
}
void prepend(T value)
{
if (this->head_ == NULL)
{
this->head_ = new Node<T>(value);
this->tail_ = this->head_;
}
else
{
this->head_->previous = new Node<T>(value);
this->head_->previous->next = this->head_;
this->head_ = this->head_->previous;
}
this->size_ += 1;
}
Node<T> * iterate()
{
if (this->itr_ == NULL)
{
this->itr_ = this->head_;
}
else
{
this->itr_ = this->itr_->next;
}
return this->itr_;
}
T ptr()
{
return this->itr_->value;
}
void resetIterator()
{
this->tail_ = NULL;
}
};
int main(int argc, char **argv)
{
LinkedList<int> llist;
llist.append(10);
llist.append(12);
llist.append(14);
llist.append(16);
llist.prepend(5);
llist.prepend(4);
llist.prepend(3);
llist.prepend(2);
llist.prepend(1);
cout << "Printing Linked List" << endl;
while(llist.iterate() != NULL)
{
cout << llist.ptr() << "\t";
}
cout << endl;
return 0;
}
这里我们使用了一个叫做节点的支持结构。该节点将携带指向下一个项目和上一个项目的指针。通常有下一个项目就足够了。但是同时拥有 next 和 previous 可以提高 append 和 prepend 的性能,因为我们可以对两端进行访问。 追加 意味着我们将更新 tail 指针的下一个元素,然后更新 tail 作为添加的项。反之, 前置 会创建一个新元素,并将其下一个项设置为当前 头 。请注意,我没有包括内存清理操作,因为我们不处理显式删除,并使代码更简单。但是,必须将它们放在单独的析构函数中,以避免内存泄漏。此外,我们使用一个简单的迭代器来打印项目。
3.大量
一个堆栈是一个 LIFO (后进先出——最后放置的元素可以首先访问)结构。尽管该数据结构具有不同的行为,但这可以被认为是仅具有头或对顶元素的访问的链表的派生。
#include <iostream>
using namespace std;
template <typename T>
class Node
{
public:
T value;
Node *next;
Node(T value)
{
this->value = value;
}
};
template <typename T>
class Stack
{
private:
int size_;
Node<T> *top_ = NULL;
Node<T> *itr_ = NULL;
public:
Stack()
{
this->size_ = 0;
}
void push(T value)
{
if (this->top_ == NULL)
{
this->top_ = new Node<T>(value);
}
else
{
Node<T> *tmp = new Node<T>(value);
tmp->next = this->top_;
this->top_ = tmp;
}
this->size_ += 1;
}
Node<T> *pop()
{
Node<T> *tmp = this->top_;
this->top_ = this->top_->next;
this->size_ -= 1; return tmp;
}
Node<T> *peek()
{
return this->top_;
}
int size()
{
return this->size_;
}
Node<T> *iterate()
{
if (this->itr_ == NULL)
{
this->itr_ = this->top_;
}
else
{
this->itr_ = this->itr_->next;
}
return this->itr_;
}
T ptr()
{
return this->itr_->value;
}
void resetIterator()
{
this->itr_ = NULL;
}
};
int main(int argc, char **argv)
{
Stack<int> stk1;
stk1.push(10);
stk1.push(12);
stk1.push(14);
stk1.push(16);
stk1.push(5);
stk1.push(4);
stk1.push(3);
stk1.push(2);
stk1.push(1);
cout << "Printing Stack" << endl;
while (stk1.iterate() != NULL)
{
cout << stk1.ptr() << "\t";
}
cout << endl; return 0;
}
注意,节点只有对下一个项目的引用。添加一个条目会更新数据结构的顶部。此外,移除和取回也是从顶部进行的。为此,我们分别使用pop()
和top()
方法。
4.行列
一个队列是一个 FIFO (先进先出——放在最前面的元素可以最先被访问)结构。这可以被认为是堆栈的相反情况。简单来说,它是一个链表,我们从一端添加,从另一端读取。这模拟了车道上的真实世界。这里我们可以把一个节点结构想成如下。
template <typename T>
class Node
{
public:
T value;
Node *next;
Node *previous;
Node(T value)
{
this->value = value;
}
};
主要的数据结构是:
template <typename T>
class Queue
{
private:
int size_;
Node<T> *head_ = NULL;
Node<T> *tail_ = NULL;
public:
Queue()
{
this->size_ = 0;
}
void enqueue(T value)
{
if (this->head_ == NULL)
{
this->head_ = new Node<T>(value);
this->tail_ = this->head_;
}
else
{
this->tail_->next = new Node<T>(value);
this->tail_->next->previous = this->tail_;
this->tail_ = this->tail_->next;
}
this->size_ += 1;
}
Node<T> dequeue()
{
Node<T> *tmp = this->tail_;
this->tail_ = this->tail->previous;
this->tail_->next = NULL; this->size_ -= 1; return tmp;
}
};
最后的想法
我们已经使用 C++编程语言实现了 4 种常见的数据结构。我将在以后的文章中介绍其余的实现。
希望你们都觉得这些数组、链表、栈和队列的 C++实现很有用。你可以通过下面的链接查看我的其他关于数据结构的文章。
8 种不同树形数据结构的概述
towardsdatascience.com](/8-useful-tree-data-structures-worth-knowing-8532c7231e8c) [## 自平衡二分搜索法树 101
自平衡二分搜索法树简介
towardsdatascience.com](/self-balancing-binary-search-trees-101-fc4f51199e1d)
感谢您的阅读!如果你觉得这篇文章有用,请在你的网络中分享。
干杯!
Python 中的数据结构——简介
当涉及到数据结构时,没有放之四海而皆准的模型。
您有多个算法,这些算法的步骤需要在任何给定的时间点获取集合中的最小值。值被分配给变量,但不断修改,使你不可能记住所有的变化。解决这个问题的一种方法是将这个集合存储在一个未排序的数组中,然后每次都扫描这个集合,以找到所需的值。但是考虑到集合有 N 个元素,这将导致所需时间与 N 成比例地增加。
数据结构来拯救!让我们发明一个普通的运算,从一组元素中找出最小值。这里,数据结构是所有这些算法用来更快地找到最小元素的通用操作。
查找数据没有单一的方法。因此,当使用一个算法时,一定要理解它所使用的数据结构的种类以及它们是运算的一部分。数据结构的主要目的是加速运算。在上面的例子中,当我谈到一个未排序的数组时,那也是一个数据结构。如果您正在使用的算法不在乎更快的结果,您可以继续使用数组来获得结果。
如果数据结构是您的算法所需要的,那么必须花时间来设计和维护一个数据结构,以便查询和更新该结构变得更容易。
Python 中的数据结构
数据结构为我们提供了一种特定的存储和组织数据的方式,从而可以方便地访问和有效地使用它们。在本文中,您将了解各种 Python 数据结构以及它们是如何实现的。
一个到我的 GitHub 存储库的链接,以访问用于本演示的 Jupyter 笔记本:
[## sowmya20/DataStructures_Intro
在 GitHub 上创建一个帐户,为 sowmya20/DataStructures_Intro 开发做出贡献。
github.com](https://github.com/sowmya20/DataStructures_Intro)
概括地说,数据结构可以分为两种类型——原语和非原语。前者是表示包含简单值的数据的基本方式。后者是一种更高级、更复杂的表示数据的方式,这些数据包含各种格式的值的集合。
非原语数据结构可以进一步分为内置和用户定义的结构。Python 提供了对内置结构的隐式支持,包括列表、元组、集合和字典。用户还可以创建自己的数据结构(如堆栈、树、队列等)。)使他们能够完全控制自己的功能。
列表
列表是一个可变的序列,可以按顺序保存同类和异类数据。列表中的每个元素都分配了一个地址,称为索引。列表中的元素用逗号分隔,并用方括号括起来。
您可以添加、删除或更改列表中的元素,而无需更改其标识。以下是使用列表时常用的一些函数:
创建列表:
initial_list = [1,2,3,4]
print(initial_list)
列表可以包含不同类型的变量,即使在同一个列表中。
my_list = ['R', 'Python', 'Julia', 1,2,3]
print(my_list)
向列表中添加元素:
my_list = ['R', 'Python', 'Julia']
my_list.append(['C','Ruby'])
print(my_list)my_list.extend(['Java', 'HTML'])
print(my_list)my_list.insert(2, 'JavaScript')
print(my_list)
使用不同的函数,如插入、扩展和添加列表,输出会有所不同。
insert 函数在指定的位置/索引处添加一个元素。
append 函数将所有指定的元素作为单个元素添加。
extend 函数将逐个添加元素。
访问元素:
可以使用方括号对列表进行索引,以检索存储在某个位置的元素。列表中的索引返回该位置的整个项目,而在字符串中,返回该位置的字符。
从列表中删除元素:
再一次,注意使用不同的函数时的输出,比如 pop、delete 和 remove。当您希望通过指定元素的值来移除元素时,可以使用 Remove。我们使用 del 通过索引移除元素,如果需要返回值,使用 pop()通过索引移除元素。
切片列表:
索引仅限于访问单个元素,而切片则访问列表中的一系列数据。
切片是通过定义父列表中第一个元素和最后一个元素的索引值来完成的,这是切片列表中所需要的。它被写成[ a : b ],其中 a,b 是来自父列表的索引值。如果 a 或 b 未定义,则索引值被视为 a 的第一个值(如果 a 未定义),以及 b 的最后一个值(如果 b 未定义)。
排序功能:
# print the sorted list but not change the original onenumero = [1,12,4,25,19,8,29,6]
print(sorted(numero))
numero.sort(reverse=True)
print(numero)
最大、最小和 ASCII 值:
在元素为字符串的列表中, max( ) 和 min( ) 适用。 max( ) 将返回一个字符串元素,当使用 min( ) 时,其 ASCII 值最高和最低。
每次只考虑每个元素的第一个索引,如果它们的值相同,则考虑第二个索引,以此类推。
new_list = ['apple','orange','banana','kiwi','melon']
print(max(new_list))
print(min(new_list))
如果数字被声明为字符串会怎么样呢?
new_list1 =['3','45','22','56','11']
print(max(new_list1))
print(min(new_list1))
即使在字符串中声明数字,也会考虑每个元素的第一个索引,并相应地返回最大值和最小值。
您还可以根据字符串的长度找到最大值和最小值。
复制&工作列表:
虽然没有对复制的列表执行任何操作,但是它的值也已经被更改。这是因为您已经将 new_list 的相同内存空间分配给了 new_list_2。
我们如何解决这个问题?
如果你还记得的话,在切片中我们已经看到了 parent list [a:b]从 parent list 返回一个带有起始索引 a 和结束索引 b 的列表,如果没有提到 a 和 b,那么默认情况下它会考虑第一个和最后一个元素。我们在这里使用相同的概念。
元组
元组用于将多个对象组合在一起。与列表不同,元组是不可变的,并且在圆括号而不是方括号中指定。元组中的值不能被覆盖,也就是说,它们不能被更改、删除或重新分配。元组可以保存同类和异类数据。
创建和访问元组中的元素:
追加一个元组:
tuple_1 = (1,2,3,4,5)
tuple_1 = tuple_1 + (6,7,8,9,10)
print(tuple_1)
元组是不可变的。
Divmod 函数:
把元组想象成对某个特定的值为真,而对其他值不为真的东西。为了更好地理解,让我们使用 divmod() 函数。
xyz = divmod(10,3)
print(xyz)
print(type(xyz))
这里商必须是 3,余数必须是 1。当 10 除以 3 时,这些值无论如何都不能改变。因此,divmod 在一个元组中返回这些值。
内置元组函数:
Count 和 Index 用于元组,就像它们用于列表一样。
example = ("Mumbai","Chennai","Delhi","Kolkatta","Mumbai","Bangalore")
print(example.count("Mumbai"))print(example.index("Delhi"))
字典
如果你想实现一个类似电话簿的东西,字典是你所需要的。字典基本上存储“键-值”对。在一个电话目录中,你将拥有 phone 和 Name 作为键,而分配的各种名称和号码就是值。“键”标识项目,“值”存储项目的值。“键-值”对用逗号分隔,值用冒号“:”字符与键分隔。
您可以添加、删除或更改字典中现有的键值对。下面提到的是使用字典执行的一些常见功能。
创建字典:
new_dict = {} # empty dictionary
print(new_dict)
new_dict = {'Jyotika':1, 'Manu':2, 'Geeta':3, 'Manish':4}
print(new_dict)
添加或更改键值对:
删除键值对:
使用 pop()函数删除值,该函数返回已删除的值。
要检索键-值对,可以使用 popitem()函数,该函数返回键和值的元组。
要清除整个字典,可以使用 clear()函数。
new_dict_2 = new_dict.pop('Manu')
print(new_dict_2)
new_dict_3 = new_dict.popitem()
print(new_dict_3)
Values()和 keys()函数:
values()函数返回字典中所有赋值的列表。
keys()函数返回所有的索引或键,这些索引或键包含它被赋予的值。
print(new_dict.values())
print(type(new_dict.values()))
print(new_dict.keys())
print(type(new_dict.keys()))
设定
集合是唯一元素的无序集合。集合是可变的,但在数据集中只能保存唯一的值。集合运算类似于算术运算。
new_set = {1,2,3,3,3,4,5,5}
print(new_set)
new_set.add(8)
print(new_set)
对集合的其他操作:
。union() —合并两个集合中的数据
。intersection() —输出两个集合共有的数据
。difference() —删除两者中存在的数据,并输出仅存在于传递的集合中的数据。
。symmetricdifference() —删除两个集合中存在的数据,并输出两个集合中剩余的数据。
用户自定义数据结构概述
- 堆栈:基于 FILO(先入后出)和 LIFO(后进先出)的原则,堆栈是线性数据结构,其中新元素的添加伴随着从另一端的等量移除。堆栈中有两种类型的操作:
a)推送——将数据添加到堆栈中。
b)弹出—从堆栈中删除数据。
来源:https://en . Wikipedia . org/wiki/Stack _(abstract _ data _ type)
我们可以使用 Python 库中的模块和数据结构来实现堆栈,即 list、collections.deque、queue.LifoQueue。
2.队列:队列是一种基于先进先出原则(FIFO)的线性数据结构。首先输入的数据将首先被访问。队列上的操作可以从头到尾两端执行。入队和出队是用于从队列中添加或删除项目的操作术语。与堆栈类似,我们可以使用 Python 库中的模块和数据结构来实现堆栈,即— list,collections.deque。
来源:https://www.guru99.com/python-queue-example.html
3.树:树是由根和节点组成的非线性数据结构。数据的起源点称为父节点,随后出现的每一个其他节点都是子节点。最后的节点是叶节点。节点的级别显示了树中信息的深度。
来源:https://sites . Google . com/site/learnwithdatasures/content/graphs
4.图形:python 中的图形通常存储称为顶点(节点)和边(边)的点的数据集合。可以使用 python 字典数据类型来表示图表。字典的键表示为顶点,值表示顶点之间的边。
来源:https://codepumpkin.com/graph/
数据结构有助于组织信息,不管你是编程新手还是老手,你都不能忽视数据结构的关键概念。
有关 Python 中使用的不同数据结构的更详尽的介绍,请参考以下链接:
本书 一个字节的 Python 。
https://docs.python.org/3/tutorial/datastructures.html
Python 中的数据结构
丁满·克劳斯在 Unsplash 上的照片
精通列表——Python 最通用的数据结构
这篇文章是关于列表的。它们是 Python 中最通用、最足智多谋的内置数据结构。它们可以同时保存异构数据,即整数、浮点、字符串、NaN、布尔、函数等。在同一个列表中。它们是条目的有序序列,这意味着在访问列表时元素的顺序是保持不变的。它们是可变的,即你可以改变(添加、删除、修改)列表中的任何一项。它们可以有旧的重复项,不像“集合”Python 中的另一种数据结构。
读完这篇文章后,你将对 Python 列表有一个清晰的理解并有能力在高级水平上工作。
我将讨论以下主题:
- 创建列表并添加元素
- 访问列表元素
- 删除列表元素
- 插入元素
- 列表运算
- 反转列表
- 排序列表
- 某项的索引
- 盘点列表中的项目频率
- 列举理解
- 复制列表
- 嵌套列表
1)创建列表并添加元素
首先,我们初始化一个名为“数据”的空列表。该列表是使用方括号创建的。自然,空列表的长度为零。
data = []len(data)
>>> 0# Check the type of the variable 'data'
type(data)
>>> list
现在让我们将第一个元素添加到这个列表中。这是使用 append() 函数完成的。你会注意到它的长度现在变成了 1。
data.append(100)data
>>> [100]len(data)
>>> 1
让我们添加第二个元素。它将被附加(添加)在列表的末尾。同样,您可以添加任意多的元素。
data.append(200)
data
>>> [100, 200]len(data)
>>> 2data.append(300)
data
>>> [100, 200, 300]
当您事先不知道列表中有多少元素时,追加功能很有用。例如,要存储每小时进入商店的人数,需要追加每小时的顾客人数。但是,如果你只是主持了一次考试,你就知道到底有多少学生写了试卷。现在,如果你想在一个列表中存储他们的成绩,而不是追加,你可以初始化你的列表。
grades = [70, 100, 97, 67, 85]len(grades)
>>> 5
**不用担心!**您仍然可以使用 append 向已经初始化的列表中添加更多的元素。只需简单地使用data.append(80)
来添加第六个学生的成绩,它将被附加在列表的末尾。
如何一次性将两个或两个以上学生的分数相加?
假设你想同时追加三个学生的分数。您不能使用 grades.append(99,100,95) ,因为**“append”只接受一个参数**。您将不得不使用“追加”功能三次。
在这种情况下,您可以使用 extend() 而不是追加三次。您需要将这三个元素放在一个元组形式中(一个 iterable)。
注意不能使用 extend 来追加单个元素,即data.extend((90))
不起作用。
grades = [70, 100, 97, 67, 85]
grades.extend((99, 100, 95))
print (grades)
>>> [70, 100, 97, 67, 85, 99, 100, 95]
现在你会问,“为什么我们不能一次追加三个等级?”
可以,但是有一个条件。如下所示,一起插入的三个等级在主列表中显示为一个列表。这样的列表被称为**【嵌套列表】**。我将在本文的最后一节展示更多的例子。
grades = [70, 100, 97, 67, 85]
grades.append([99, 100, 95])
print (grades)
>>> [70, 100, 97, 67, 85, [99, 100, 95]] # A nested list
2)访问列表元素
如果你正在处理数据结构,了解索引的概念是非常有用的。你可以认为索引是分配给列表中每个元素的序列号。简单来说,就是类似于你在一个班级的点名。
最重要的 要知道的是,Python 中的 索引是从 0 开始的。
因此,第一个元素的索引为 0,第二个元素的索引为 1,依此类推。在五个元素的列表中,最后一个(第五个)元素的索引值为 4。
*grades = [70, 100, 97, 67, 85]# First element (index 0)
grades[0]
>>> 70# Second element (index 1)
grades[1]
>>> 100# Last element (index 4)
grades[4]
>>> 85*
不要越界。如果你试图使用一个大于列表长度的索引值,你将得到一个 IndexError 。因此,在一个包含 5 个元素的列表中,不能使用索引 5(因为它引用了第 6 个元素)。
*grades[5]
**-----------------------------------------------------------------**
**IndexError** Traceback (most recent call last)
**<ipython-input-29-d8836f1h2p9>** in <module>
**----> 1** data**[5]****IndexError**: list index out of range*
访问列表的多个元素
如果您想要前三个元素,您可以使用切片来访问它们。一般格式是list[start_index:end_index]
。这里比较棘手的部分是这个符号会返回值,直到 end_index - 1 的索引值。这意味着,要获得索引为 0、1 和 2 的列表的前三个元素,需要以下方法。
*grades = [70, 100, 97, 67, 85]
grades[0:3]
>>> [70, 100, 97]*
如果您只想访问索引为 0 的第一个元素,可以使用切片符号来获取它。
*grades[0:1]
>>> [70]*
反向索引
现在你也应该知道我称之为*【反向索引】或【负索引】*的有用概念。
要访问最后一个元素,首先需要知道列表的长度。因此,在 5 个元素的列表中,需要使用索引 4(= 5–1,因为索引从 0 开始)。因此,数据[4]将是您的最后一个元素。类似地,倒数第二个元素将是 data[3],依此类推。
如您所见,这种计算很麻烦。负指数将在这里帮助你。简单来说就是从末尾开始数元素个数。最后一个元素不能在索引-0 处,因为没有这样的数字。因此,您需要使用索引值-1 来访问它。类似地,倒数第二个元素可以使用索引值-2 来访问,依此类推。
*grades = [70, 100, 97, 67, 85]
grades[-1]
>>> 85grades[-2]
>>> 67*
“负索引可以用来访问最后 3 个元素吗?”。
是的。您需要指定起始负索引。要获取从倒数第三个元素开始的所有元素,不需要指定结束索引。
*grades = [70, 100, 97, 67, 85]
grades[-3:]
>>> [97, 67, 85]*
但是,假设您想要获取倒数第三个和倒数第二个元素,而不是最后一个元素。您可以将结束索引限制为:
*grades[-3:-1]
>>> [97, 67]*
2.1)定期访问元素
到目前为止,您已经学习了如何访问单个元素或几个连续的元素。假设你想从列表中得到第 n 个项目。这样做的一般语法是list[start_index : stop_index : step]
。
***举例:*如果你想从第一个元素开始到第七个元素,即从[1,2,3,4,5,6,7]开始,每隔一个元素访问一次,你需要[1,3,5,7]。
*data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
data[0:7:2]
>>> [1, 3, 5, 7]*
如果你想要从第一个元素开始的整个列表中的第二个元素,你可以跳过start_index
和stop_index
。
*data[::2]
>>> [1, 3, 5, 7, 9]*
如果您想要从第二个元素开始的整个列表中的每第二个元素,使用下面的代码。
*data[1::2]
>>> [2, 4, 6, 8, 10]*
***注意:*你也可以使用data[::1]
来访问整个列表,因为这将返回从开始到结束的所有元素。
以固定的间隔向后遍历一个列表 你需要使用一个负的步长值。要获取从末尾开始的每个元素,即整个列表的逆序,请使用下面的代码。
*data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
data[::-1]
>>> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]*
若要从最后一个元素开始每隔一个元素访问一次,请使用以下代码。
*data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
data[::-2]
>>> [10, 8, 6, 4, 2]*
让我们看一个更复杂的反向遍历例子。假设你想从倒数第三个元素开始,一直到第四个元素,每隔一个元素选择一个。您的开始索引现在变为-3,停止索引变为 3(第四个元素的索引从 0 开始)。
*data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
data[-3:3:-2]
>>> [8, 6]*
3)删除列表元素
有三种方法可以从列表中删除元素。这三种方法都执行就地删除,也就是说,在删除之后,您不需要将列表重新分配给新的变量。
a) del
(内置函数)能否一次删除多个项目
b)remove()
(列表的方法)能否一次删除一个项目
c)pop()
(列表的方法)能否一次删除一个项目
让我们逐一研究它们。
a)德尔
使用del
时,需要传递要删除的元素的索引或一部分索引。您可以使用上面介绍的所有索引/切片概念,通过 del 删除元素。
*# Deleting first element
data = [79, 65, 100, 85, 94]
del data[0]
print (data)
>>> [65, 100, 85, 94] ############################################################## Deleting second last element
data = [79, 65, 100, 85, 94]
del data[-2]
print (data)
>>> [79, 65, 100, 94]############################################################# # Deleting multiple consecutive elements using slice
data = [79, 65, 100, 85, 94]
del data[0:3]
print (data)
>>> [85, 94]############################################################## # Deleting multiple elements at regular interval using slice
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
del data[1::2]
data
# [1, 3, 5, 7, 9]*
b)移除()
这个方法用于从列表中删除一个特定的元素。如果一个元素出现不止一次,那么只有第一次出现的元素会被删除。例如,在下面的列表中,1 出现了 3 次。所以使用remove(1)
将删除第一个值,保留其余的值。
*data = [1, 1, 4, 4, 3, 1, 3, 2, 4, 2]
data.remove(1)
print (data)
>>> [1, 4, 4, 3, 1, 3, 2, 4, 2]*
***奖励:*你可以使用一个 while 循环来移除所有出现的 1。
*data = [1, 1, 4, 4, 3, 1, 3, 2, 4, 2]
to_del = 1while to_del in data:
data.remove(to_del)
print (data)
>>> [4, 4, 3, 3, 2, 4, 2]*
c) pop()
这个方法的语法是list.pop(i)
,它从列表中弹出(删除)索引‘I’处的元素。下面的代码演示了它在连续应用于一个列表时是如何工作的。
****注意:如果不指定索引,最后一个元素将被删除。
***# Using pop(i) by specifying the index i
data = [1, 2, 3, 4, 5]
data.pop(0)
print (data)
>>> [2, 3, 4, 5]data.pop(1)
print (data)
>>> [2, 4, 5]data.pop(1)
print (data)
>>> [2, 5]############################################################## Using pop() without specifying the index i
data = [1, 2, 3, 4, 5]
data.pop()
print (data)
>>> [1, 2, 3, 4]data.pop()
print (data)
>>> [1, 2, 3]***
4)插入元素
可以使用功能list.insert(i, element)
在指定位置插入一个元素。这里的‘I’是列表中要插入element
的现有元素的索引。正如您之前看到的,append()
函数将元素插入到列表的末尾。
注意:这是一个就地操作,所以你不必重新分配列表。
***# Inserting value of 4 at the start, before the element at index 0
data = [1, 2, 3]
data.insert(0, 4)
print (data)
>>> [4, 1, 2, 3]############################################################## Inserting value of 4 before the element at index 1
data = [1, 2, 3]
data.insert(1, 4)
print (data)
>>> [1, 4, 2, 3]***
如果你想在列表的末尾插入,也就是说,追加,那么简单地使用列表的长度作为插入的位置。
***data = [1, 2, 3]
data.insert(len(data), 4)
print (data)
>>> [1, 2, 3, 4]***
5)列表算法
当您添加两个或更多列表时会发生什么?假设您有以下两个列表。
***list_A = [1, 2, 3, 4, 5]
list_B = [6, 7, 8, 9, 10]***
如果您添加它们,您会期望两个列表的元素相加。但是,您将得到一个列表,其中两个列表的元素按照相加的顺序被附加(连接)在一起。
***list_A + list_B
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]***
顺序很重要。对于列表来说,A + B 不等于 B+A所以,如果颠倒加法的顺序,就会得到不同的结果。****
*****list_B + list_A
>>> [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]*****
你能做两个列表的减法、乘法或除法吗?
不,你不能。举个例子,如果你试着把上面两个列表相减,你会得到一个TypeError
。类似地,乘法或除法也会抛出错误。尝试一下说服自己。
如果你将列表乘以一个大于 0 的正整数,你的列表将重复(复制)那么多次。例如,将列表乘以 3 会将列表重复 3 次。乘以浮点数(3.0)会产生错误。将列表乘以 0 或负整数产生一个空列表。****
*****# Multiplication by positive integer
data = [1, 2, 3]
data * 3 # equivalent to data + data + data
>>> [1, 2, 3, 1, 2, 3, 1, 2, 3]# Multiplication by 0
data * 0
>>> []*****
****注意:如果您试图乘以[3]而不是 3,将会报告错误。
6)反转列表
有两种方法可以反转列表。
a)使用切片作为[::-1]。这不会就地更改列表。您必须重新分配列表,以反映原始列表中的更改。****
*****data = [1, 2, 3]
data[::-1]
>>> [3, 2, 1]print (data)
>>> [1, 2, 3] # The original list does not change# You have to re-assign the list after reversing
data = [1, 2, 3]
data = data[::-1] # Re-assign print (data)
>>> [3, 2, 1]*****
b)使用**list.reverse()**
功能。在这里,您不需要重新分配,因为列表是就地反转的。
*****data = [1, 2, 3]
data.reverse() # reverses the list in-place print (data)
>>> [3, 2, 1] # The original list does not change*****
7)对列表进行排序
对列表进行排序有两种直接的方法。
a)使用**sorted()**
功能。这不会对列表进行就地排序。
b)使用**list.sort()**
功能。执行就地排序。
在这两个函数中,您可以使用关键字“reverse”选择升序或降序排序。如果“reverse=True”,则列表按降序排序。默认情况下,列表按升序排序。
*****# First method using sorted()
data = [7, 4, 1, 3, 8, 5, 9, 6, 2]
sorted(data, reverse=False) # Same as sorted(data) due to default
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9]############################################################## Second method using list.sort()
data = [7, 4, 1, 3, 8, 5, 9, 6, 2]
data.sort(reverse=True)
print (data)
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9]*****
字符串是如何排序的?
- 默认情况下,不同长度的字符串将按字母顺序排序。
- 如果字符串的起始字母相同,但大小写不同,则大写字母优先。
- 如果两个或两个以上的字符串具有相同的大小写首字母,它们将根据第二个字母按字母顺序排序,依此类推。
让我们看一些使用第二种方法的字符串示例。
*****data = ['pineapple', 'kiwi', 'apple', 'azure', 'Apricot', 'mango']
data.sort() # in-place sorting
print (data)
>>> ['Apricot', 'apple', 'azure', 'kiwi', 'mango', 'pineapple']#############################################################data = ['pineapple', 'kiwi', 'apple', 'Apricot', 'mango']
data.sort(reverse=True)
print (data)
>>> ['pineapple', 'mango', 'kiwi', 'apple', 'Apricot']*****
如何根据字符串的长度进行排序?
你需要使用关键字key=len
。如果你想要长度降序排列,使用一个额外的关键字reverse=True
。
*****data = ['pineapple', 'kiwi', 'apple', 'Apricot', 'orange', 'mango']
data.sort(key=len, reverse=True)
print (data)
>>> ['pineapple', 'Apricot', 'orange', 'apple', 'mango', 'kiwi']*****
8)项目的索引
如果您想获得给定列表中某个条目的索引,可以使用命令list.index(item)
来实现。它在整个列表中搜索项目。如果同一个项目出现多次,您将只获得它第一次出现的索引。
*****grades = [70, 100, 97, 70, 85]
grades.index(100)
>>> 7grades.index(70) # 70 appears twice at indices 0 and 3
>>> 0 # Only the first index returns*****
假设您的列表非常大,并且您希望只在列表的特定子集中搜索一个元素。您可以为子集指定“开始”和“结束”索引。
*****grades = [70, 100, 97, 70, 85, 100, 400, 200, 32] # Search in the whole list
grades.index(100)
>>> 7# Search in the partial list from index 3 until index 8grades.index(100, 3, 8)
>>> 5 # Now the index of the second 100 is returned*****
9)统计列表中的项目频率
您可以使用list.count(item)
统计列表中给定项目的出现频率。让我们考虑下面的例子。
*****data = [6, 4, 1, 4, 4, 3, 4, 8, 5, 4, 6, 2, 6]
data.count(4)
>>> 5*****
功能使用示例
假设您想要计算并打印所有元素的频率。为此,我们首先需要列表中的唯一项目。我会用 NumPy 的unique()
。
*****import numpy as npdata = [1, 1, 4, 4, 3, 1, 3, 2, 4, 2]for item in np.unique(data):
print("{} occurs {} times in the list"\
.format(item, data.count(item)))>>> 1 occurs 3 times in the list
>>> 2 occurs 2 times in the list
>>> 3 occurs 2 times in the list
>>> 4 occurs 3 times in the list*****
10)列出理解
假设您想计算从 0 到 5 的数字的立方,并将它们存储在一个列表中。首先需要初始化一个空列表,创建一个 for 循环,然后将各个数字的立方体追加到这个列表中。
*****cubes = []
for i in range(6):
cubes.append(i**3)
print (cubes)
>>> [0, 1, 8, 27, 64, 125]*****
上面的代码对于这么简单的任务来说太多了吧?嗯,你可以简单地使用【列表理解】如下所示。
*****cubes = [i**3 for i in range(6)]
print (cubes)
>>> [0, 1, 8, 27, 64, 125]*****
11)复制列表
假设你有一个名为‘list _ A’的列表,你把这个列表分配给另一个名为‘list _ B’的列表。如果您从“list_A”中删除一个元素,您会期望“list_B”不会改变。下面的代码表明事实并非如此。从“list_A”中删除元素也会将其从“list_B”中删除。
*****list_A = [1, 2, 3, 4, 5]
list_B = list_Adel list_A[0] # Delete an element from list_Aprint (list_A, list_B)
# [2, 3, 4, 5] [2, 3, 4, 5]*****
为什么 list_B 会受到影响?
这是因为当你写list_A = list_B
的时候,你创建了一个对‘列表 _ A’的引用。因此,‘list _ A’中的变化也会反映到‘list _ B’中的。
如何避免 list_B 的变化?
答案是创建一个浅拷贝。我将解释做这件事的两种方法。
a)使用list.copy()
b)使用list[:]
下面的例子表明,现在从‘list _ A’中删除一个元素并不影响浅层拷贝,即‘list _ B’。
*****# First method using list.copy()
list_A = [1, 2, 3, 4, 5]
list_B = list_A.copy()del list_A[0] # Delete an element from list_A
print (list_A, list_B)
# [2, 3, 4, 5] [1, 2, 3, 4, 5]#############################################################
# Second method using list[:]
list_A = [1, 2, 3, 4, 5]
list_B = list_A[:]del list_A[0] # Delete an element from list_A
print (list_A, list_B)
# [2, 3, 4, 5] [1, 2, 3, 4, 5]*****
12)嵌套列表
包含另一个子列表作为元素的列表称为嵌套列表。元素子列表可以包含更多的子列表。子列表中的元素也可以使用索引和切片来访问。让我们考虑下面的例子。****
*****data = [1, 2, 3, [4, 5, 6]]data[2] # Single element
>>> 3 data[3] # Sublist
>>> [4, 5, 6]*****
现在的问题是,“如何访问子列表的元素?”。您可以使用双重索引来访问它们。例如,data[3]返回子列表。所以这个子列表的第一个元素可以使用 data[3][0]来访问。
*****data[3][0]
>>> 4data[3][1]
>>> 5data[3][2]
>>> 6*****
现在考虑下面的列表,它在子列表中有一个子列表。列表的长度是 4,其中前 3 个元素是 1、2 和 3,最后一个元素是[4、5、6、[7、8、9]]。最后一个元素的长度是 4,它是一个子列表。这样,你就可以越来越深入到嵌套列表中。****
要访问子列表的元素,您需要使用双索引、三索引等。如下例所示。****
*****data = [1, 2, 3, [4, 5, 6, [7, 8, 9]]] # A nested list# Length of the list
len(data)
>>> 4# The last element
data[3]
>>> [4, 5, 6, [7, 8, 9]]# Length of the last element
len(data[3])
>>> 4#############################################################
# Accessing the elements of the first sublist
data[3][1] # Double indices
>>> 5data[3][3] # Double indices
>>> [7, 8, 9]#############################################################
# Accessing the elements of the second sublist
data[3][3][0] # Triple indices
>>> 7data[3][3][-1] # Triple indices
>>> 9*****
这让我想到了这篇文章的结尾。我介绍了与列表相关的大部分操作,现在读者应该对 Python 中的列表有了进一步的了解。如果你有兴趣了解即将到来的 Python 3.10 版本和 Matplotlib 3.0 中的新特性,请参考以下帖子。****
***** [## 2021 年的 Python 时间表和即将推出的功能
Python 3.10 的一些新特性的预览
towardsdatascience.com](/python-in-2021-timeline-and-upcoming-features-c8369abbcc52) [## Matplotlib 3 的新特性
第 3 代中最重要的更新概述
towardsdatascience.com](/whats-new-in-matplotlib-3-1b3b03f18ddc)*****