骑着 Python 穿越 Peloton
实践教程
使用基础数据分析对环法自行车赛进行分析
第一阶段:介绍
每年夏天,世界顶尖的自行车选手聚集在法国,进行一场激烈的全国比赛。再过几天,2021 年环法自行车赛将在小港口城市布列斯特开始,并像往常一样,在几周后的巴黎结束。鉴于“大数据”和“分析”的趋势正在触及体育世界的每一个部分,包括自行车,我想使用 Python 中可用的流行库组合来更深入地了解这个年度赛事。这篇文章旨在构建对历史环法自行车赛数据的现有分析,同时展示 Python 在数据分析和可视化基础方面的强大功能。
第二阶段:读入并探索数据
我们分析使用的主要数据集直接来自环法自行车赛官方网站,其中包含了从 1903 年到 2020 年的每场比赛的骑手信息(感谢杰里米·辛格-万的数据是多元的时事通讯来呈现数据集!).由于托马斯·坎米纳迪令人印象深刻的网络搜集——这是一个值得完全独立发布的有用技能组合——这些数据已经被格式化为一个方便的 CSV 文件,详细列出了每个骑手的等级、时间、团队等等。
图:数据来源网站(letour.fr/en)
我们将从 Python 中导入我们分析所需的特定库,然后使用 read_csv 方法从 Pandas 库中读入数据。
图:我们的数据帧的前五行
的。info 方法不仅详细说明了数据集的大小(9,452 行和 18 列),还详细说明了每列的数据类型以及每列缺失的数据量。粗略地看一下,我们发现“奖金”和“积分”变量在我们的数据准备阶段很容易被删除或估算,因为每个变量都有大量的缺失值或空值。
图:我们的列的名称、数据类型和缺失数据计数
最后。describe 方法显示了我们的数字列的快速统计摘要。虽然我们正在处理的数据集不够全面,不足以证明这些步骤,但通过热图或相关矩阵图以及直方图来可视化您的数据,以了解数据分布,可以作为探索您的数据的初始模式或关系的另一种方式。
图:我们的整数类型列的一些统计度量
第三阶段:清理和准备数据
尽管耗时且平庸,数据清理和准备对于任何适当的数据深度挖掘都是必不可少的一步,因为它创建了一个适当的基础数据集来进行未来的分析。我们将删除数据集的第一列。drop 方法,因为它不提供任何值。此外,“奖金”和“积分”列似乎没有太大的帮助,因为缺少大量的值,所以让我们也放弃这些。虽然我们不会在这里触及,但各种数据插补和替换方法可用于解决数据缺失部分。将距离从千米转换成英里可以通过在阵列上运行简单的计算来实现;我们还想用。为了清楚起见,重命名方法。
图:我们的“清理”数据集的前十行
除了列名之外,在查看“team”变量时,我们遇到了一个很好的数据清理机会。特殊团队“Touriste-Routiers”(翻译为“公路游客”)与“Touristes Routiers”分开计算。
图:初始数据集中的前 10 个团队
虽然这实际上可能是两个独立的团队——关于是否存在连字符的争论很常见——让我们继续假设这只是一个错误,我们可以使用。将方法替换为地址。
图:10 大团队,使用。替换方法
阶段 4:分析数据
在这一点上,我们可以开始问——并希望回答——一些关于环法自行车赛的简单的时间序列问题。除了季节性和变化率之外,总体趋势也是时间序列分析的共同兴趣点。例如,如果我们想了解比赛的规模是如何增长的,我们可以使用。按 and 分组。数数每年乘坐的人数。
图:每年乘客总数
我们创建的图表看起来非常简单;我们通常认为随着赛事和运动的流行,比赛的规模会随着时间的推移而增加。然而,1966 年骑手数量的大幅下降有点不寻常。那年的比赛看起来没什么特别的(嗯,除非你考虑到引入兴奋剂检测和参赛者的中途抗议)。巡回赛的统计学家放暑假了吗?数据记录是否被一名当年未获得参赛资格的复仇但精通网络的骑手抹去了?撇开阴谋论不谈,这种奇怪的现象确实值得再研究一下。如果我们好奇哪些车手参加了最多的环法自行车赛,环法自行车赛,环法自行车赛?让我们来看看比赛…
图:数据集中出现频率最高的骑手
看起来法国自行车手西尔万·查瓦内尔以 16 件参赛作品领跑!让我们稍微解开这一行代码…
- rider_df['rider]从我们的数据帧中选择“Rider”列,生成一个序列
- 。values_counts()是对这个新系列中的值进行计数的方法;这实际上创建了一个新的序列,其中索引是附加项,值是它们出现的频率(从最频繁开始递减)
- [:10]给出了这个系列中前 10 个最常出现的骑手;最终创建最终系列,如上所示
如果我们想快速查看每年的所有获奖者,该怎么办?我们只需创建一个列表,并在“Rank ”= 1 时进行过滤。通过过滤选择数据子集是回答此类问题或生成新问题的一个好方法。
图:显示获胜者的数据子集
换挡(不得不偷偷把至少一个糟糕的骑车双关语放到这篇文章中),让我们看看光谱的另一端,按速度排序数据,看看历史上最慢的骑手。这可以通过。sort_values 方法应用于适当的列,在我们的例子中是“PersonalAvgPace”。
图:基于“个人空间”变量的最慢骑手
不出所料,最慢的车手名单经常出现在一些最早记录的比赛中。
阶段 5:将数据与另一个数据集合并
我们的原始数据集是全面的,但当涉及到每个骑手的有趣信息时,就相当有限了。幸运的是,我们在 Github 上看到了一个单独的数据集,列出了每年环法自行车赛的冠军,包括成绩信息以及骑手的身高、体重、年龄、国籍等等。 Python 让我们能够转换现有的骑手数据框架,并将两个数据集合并在一起。
我们的原始数据集已被修改为只显示获胜者,并被重命名为 df2 现在,我们将阅读并检查我们遇到的其他赢家数据集。
图:我们新数据集的初步观察
我们将在这个新数据集上执行一组类似的数据清理和准备活动,就像我们对原始数据集所做的那样。Year 显然是连接两组数据的主键,但是我们需要确保它们的格式相同。让我们用一个快速切片来修剪“Year”列(当我们使用 quick .str.upper 方法时,用同样的方式来格式化 riders)。确保数据类型相同很重要,因此我们将确保“Year”变量是一个带有。a 类型方法。
图:我们的两个数据框,准备合并
类似于 SQL 的 JOIN 子句。Pandas 中的 merge 方法允许在一列或多列上连接两个数据帧。默认的合并类型是内部联接,但是可以使用 how 参数覆盖它。我们知道获胜者/骑手的名字是多余的,因为它包含在两个数据集中;在 Python 中,列上的 _x 和 _y 后缀有助于指示列名的来源(x 是左边的数据集,而 _y 是右边的)。我们将删除其中一个,并在最终的合并数据集中有一个单独的“获胜者”列。
图:我们新合并的数据框架
我们成功合并的数据集为环法自行车赛获胜者开辟了许多新的探索途径。自行车运动中的“团队”概念一直是我感兴趣的,也是我不太了解的东西。一个与团队相关的问题可能是“从一个国家到另一个国家,哪个团队最成功?”为了回答这个问题,我们可以安排我们的数据来看看团队获胜,按国籍分组。
图:我们输出的一个子集,按国家列出了获胜团队的名称
阶段 6:用 Matplotlib、Seaborn 和 Plotly 可视化数据
Python 提供了各种绘图库来帮助你创建清晰、深刻的数据可视化。在这个阶段,我们将展示一些最流行的库的功能,包括 Matplotlib、Plotly 和 Seaborn。让我们用一个简单的线形图来显示获胜者每年的平均速度。我们将添加一些参数,如绘图标题、轴标签和图例。我们还将添加一个注释来记录一段缺失的数据。
图:显示每年获胜者平均速度的线形图
毫不奇怪,随着时间的推移,比赛获胜者的速度越来越快,近年来的增长略有放缓,这可能是由于近年来对兴奋剂的打击以及骑手和自行车不断达到的身体限制的综合作用。如果我们想将这些信息与另一个不同尺度的变量进行比较,比如距离,会怎么样呢?我们只需用另一个轴创建一个图来容纳我们的新变量。我们还将确保合并标签,这样两行都可以考虑。
图:比较比赛距离和冠军配速
条形图是 Python 中可视化数据的常用方式。让我们使用此图表类型按获胜次数来比较获胜者。
我们已经可以看出这个图表不是最直观或最容易阅读的。无意冒犯许多只赢了一场比赛的车手,但让我们看看是否能把它精简到赢了多次的车手。通过对数据和标签的一些调整,我们应该能够创建一个更容易解释的图表。(点击这里查看一篇更详细的帖子,展示了构建更好图表的各个层次)
图:显示多个赢家的改进条形图
也许我们想显示一段时间内环法自行车赛“平均”获胜者的数据。创建一些变量并将它们传递到 print 语句中是一个好的开始。
让我们试着用一种更具视觉吸引力的形式来表现“普通”骑手。直方图是查看数据频率分布的有用方式。的。hist()函数有许多选项来改变数据的计算和相应图表的显示。如果我们假设我们的数据是正态分布的,一些直方图将有助于描绘一幅“平均赢家”的图画。Matplotlib 的 subplot 特性允许我们并排比较这些图表。
图:环法自行车赛获胜者平均值直方图的子图
简单地说,散点图是展示两个变量之间关系的好方法。也许我们想要一个关于获胜者身高和体重之间相关性的视觉表现。
图:身高和体重的散点图
这是一个好的开始,但让我们做一些挖掘来注释上图中的一个异常值,特别是体重最大的骑手。对我们的数据进行一些快速过滤,很快就确定了这个异常值是 1909 年环法自行车赛冠军弗朗索瓦·费伯(Francois Faber)的名字,他被恰当地称为“哥伦布巨人”。让我们对 Francois 在领奖台上穿着 XL 黄色球衣给予应有的认可,并用注释更新我们的散点图。
据其网站,“Seaborn 是一个基于 matplotlib 的 Python 数据可视化库。它提供了一个高级界面,用于绘制有吸引力且信息丰富的统计图形。”虽然 matplotlib 中的一些绘图类型非常令人满意,但 Seaborn 为其他绘图类型提供了轻微的视觉增强,希望能够提高理解能力。我们将使用 boxplots 来展示 Seaborn 的功能,这是一种非常好的异常检测方法。下图显示了 Seaborn 如何对按国籍分组的平均获奖者年龄的箱线图进行细微的视觉改进。
图:使用 matplotlib 的箱线图
图:使用 seaborn 的箱线图
虽然我们的样本量相对较小,但深入挖掘箱线图中显示的意大利获胜者的异常值会很有意思。最后但同样重要的是, Plotly 是一个图形库,允许用 Python 创建交互式图表。Plotly 在多种格式的可读性方面大放异彩,它为各种类型的情节提供了许多有趣的定制功能,但我们将使用下面的代码创建一个带有交互式悬停状态的简单的 winners age 折线图。
图:显示每年获奖者年龄的图表
阶段 7:未来分析
如前所述,这个项目在范围上相当有限,只是触及了可以在这样的数据集上运行的分析的表面。未来分析中涉及的几个有趣的问题或概念包括:
- 预测:时间序列分析的首要目标往往是预测;观察未来 15 年的冠军配速或比赛距离等因素可能会很有趣。
- 回归模型:建立多元线性回归模型,根据身高、体重、年龄、国籍等因素预测骑行者的步伐。
- 异常检测:查看哪些骑手或获胜者在人群中脱颖而出,并找出原因将是扩展我们上面展示的一些异常检测工作的一个好方法。
- 对比被禁赛车手的表现 : 兴奋剂指控和争议从环法自行车赛开始就一直困扰着它;被指控服用兴奋剂的被取消资格的骑手没有被包括在数据集中,但比较被禁赛的骑手被剥夺冠军头衔的速度和我们数据集中的获胜者的速度等指标会很有趣
- 雷达图:绘制多个雷达(或蜘蛛)图比较不同国籍的车手在各种不同因素上的表现。
当你加入生物特征数据、天气、海拔和自行车类型等因素时,在未来几年的环法自行车赛中,将会发现大量有趣且有望产生影响的见解。
第八阶段:进一步阅读和总结
如上所述,这不是第一次深入研究环法自行车赛的数据,希望不会是最后一次!如果你对这些感兴趣的话,我在研究过程中发现了更多的资源:
- 数字环法自行车赛简介
- 环法自行车赛数据分析,使用 Jupyter 笔记本中的 Strava 数据以及 Python、Pandas 和 Plotly
- 可视化环法自行车赛
- #tidytuesday:环法自行车赛冠军
希望您喜欢这个高层次的概述,并发现它对展示 Python 及其相关特性的简单方面如何揭示数据集的独特见解有所帮助。点击查看原始代码。我错过/忘记了什么,或者你会采取什么不同的做法?任何和所有的反馈都欢迎通过下面的评论,或者在 wmc342@gmail.com 给我留言。非常感谢您的阅读,并享受比赛!🚴
Python 熊猫面试数据科学的问题
利用熊猫进行 Python 数据科学面试试题
在 Unsplash 上由Christina @ wocintechchat.com拍摄的照片
在上一篇文章 Python Pandas 数据科学面试问题第 1 部分 中,我们研究了如何将数据导入 Pandas 并执行基本计算,例如
- 排序数据帧
- 处理重复
- 聚集
- 合并数据帧
- 计算字段
在这一部分中,我们将基于这些知识,用它们来解决更复杂的 Python 熊猫面试问题。我们将关注以下领域
- 日期时间操作
- 文本操作
- 应用函数
- 高级聚合
- 偏移数据
- 使用熊猫的统计
如果你不熟悉熊猫图书馆,请浏览那篇文章。让我们开始吧。
应用函数
在本系列的前一部分中,我们研究了如何创建额外的字段。我们也可以使用 Python 库中的函数或用户定义的函数来操作值。为此,我们使用 apply()方法。apply()方法使用矢量化,因此与遍历序列中的每一行相比,可以更快地计算值。这里有一个来自 AirBnB 数据科学采访的简单问题。
生活设施最多的城市
从 Airbnb 上给定的房产搜索数据集中,找出主人所有房产中设施最多的城市。假设每行代表一个唯一的主机。输出城市名称作为您的解决方案。
截图来自 StrataScratch
你可以在这里解决这个 Python 熊猫面试问题。https://platform . stratascratch . com/coding/9633-拥有最多便利设施的城市?python=1
该问题使用包含这些字段的 airbnb_search_details 数据集
截图来自 StrataScratch
以下是数据的显示方式。
截图来自 StrataScratch
方法和解决方案
让我们把这个问题分解一下。给定酒店的所有便利设施都在便利设施栏中给出。便利设施以逗号分隔的字符串形式列出。我们通过简单地使用逗号分隔符分割字符串并取结果列表的长度来找到便利设施的数量。
# Import your libraries
import pandas as pd
# get the number of number of amenities
airbnb_search_details['num_amenities'] = airbnb_search_details
['amenities'].apply(lambda x : len(x.split(",")))
airbnb_search_details[['city', 'id', 'amenities', 'num_amenities']]
数据集现在看起来像这样。
截图来自 StrataScratch
我们现在可以简单地合计每个城市的设施数量,并输出设施数量最多的城市。我们可以使用方法链接将所有这些方法组合在一行中。
# Summarize by city and output the city with the most amenities
airbnb_search_details.groupby(by = ['city'], as_index = False).agg
({'num_amenities' : 'sum'}).sort_values(by = ['num_amenities'],
ascending = False).reset_index()['city'][0]
我们不局限于内置函数。我们可以创建自己的函数,也可以动态创建 lambda 函数。让我们在下面的 Python 熊猫采访问题中尝试一下。这个出现在旧金山数据科学采访中。
划分业务类型
将每家企业归类为餐馆、咖啡馆、学校或其他。被归类为餐馆的企业名称中应包含“餐馆”一词。对于咖啡馆,企业名称中应包含“咖啡馆”、“咖啡馆”或“咖啡”。学校会有‘学校’这个词。根据上述规则,如果企业不是餐馆、咖啡馆或学校,则应归类为“其他”
截图来自 StrataScratch
你可以在这里解决这个 python 面试问题。https://platform . stratascratch . com/coding/9726-classify-business-type?python=1
此问题使用具有以下字段的SF _ restaurant _ health _ violations数据集。
截图来自 StrataScratch
数据是这样的。
截图来自 StrataScratch
方法和解决方案
这个 Python Pandas 面试问题的唯一相关字段是 business_name 列。我们编写一个 lambda 函数,并使用 apply 方法来检查企业满足了哪些业务规则。一旦我们有了类别,我们就输出问题中所需的相关字段。
# Import your libraries
import pandas as pd
# Classify as per the rules
sf_restaurant_health_violations['category'] = sf_restaurant_health_violations
['business_name'].apply(lambda x: \
'school' if x.lower().find('school') >= 0 \
else 'restaurant' if x.lower().find('restaurant') >= 0 \
else 'cafe' if (x.lower().find('cafe') >= 0 or x.lower().find('café') >= 0 \
or x.lower().find('coffee') >= 0) \
else 'other'
)
# Output relevant fields
sf_restaurant_health_violations[['business_name', 'category']].drop_duplicates()
正如我们所见,apply 方法是使用用户定义的函数或 Python 库中的函数操作值和创建计算字段的一种非常强大的方法。
高级聚合
在上一篇文章中,我们已经看到了使用 groupby 方法的聚合。Pandas 也支持其他聚合,我们还可以创建一个电子表格风格的数据透视表。让我们看看如何在熊猫身上做到这一点。我们从旧金山市的一个数据科学问题开始。
制作一张数据透视表,找出每位员工每年的最高工资
查找从 2011 年到 2014 年每年每位员工的最高薪酬。以表格的形式输出结果,列中是年份,行中是按字母顺序排序的员工姓名。
截图来自 StrataScratch
你可以在这里解决这个 Python 熊猫面试问题。https://platform . stratascratch . com/coding/10145-make-a-pivot-table-to-find-the-high-payment in-year-per-employee?python=1
该问题使用具有以下字段的 sf_public_salaries 数据集。
截图来自 StrataScratch
数据是这样显示的。
截图来自 StrataScratch
方法和解决方案
虽然这个问题对于 SQL 来说可能有点困难,但是对于 Pandas 来说,这个问题可以通过使用 pivot_table()函数在一行代码中解决。我们只需传递正确的参数,并获得想要的输出。
# Import your libraries
import pandas as pd
# create the pivot table
pd.pivot_table(data = sf_public_salaries, columns = ['year'],
index = 'employeename', values = 'totalpay', aggfunc =
'max', fill_value = 0).reset_index()
pivot_table()方法非常强大,可以帮助快速解决复杂的聚合问题。这是另一个例子。这是来自脸书数据科学的采访。
两个事件之间的时间
报告用户从页面加载到第一次向下滚动的最短时间。输出应该包括用户 id、页面加载时间、第一次向下滚动时间以及两个事件之间的时间间隔(秒)。
截图来自 StrataScratch
你可以在这里解决问题https://platform . stratascratch . com/coding/9784-time-between-two-events?python=1
这个问题使用了包含以下各列的 facebook_web_log 数据集。
数据是这样显示的。
截图来自 StrataScratch
方法和解决方案
同样,这个面试问题在 SQL 中可能有点复杂。然而,在熊猫中,使用数据透视表是相对简单的。我们首先创建一个数据透视表,其中包含每个 user_id 的每个动作的最早实例。因为我们只需要 page_load 和 scroll_down 事件,所以我们只在输出中保留那些列。
import pandas as pd
# Find the first instance of diffrent actions
summ_df = pd.pivot_table(data = facebook_web_log, index = 'user_id', columns =
'action', aggfunc = 'min', values = 'timestamp').reset_index()[['user_id',
'page_load', 'scroll_down']]
输出如下所示。
截图来自 StrataScratch
现在问题变得很简单。我们可以通过获取 scroll_down 和 page_load 时间戳之间的差值来直接计算持续时间。然后,我们输出持续时间最短的用户的 user_id 和其他必填字段。
# Caclulate duration
summ_df['duration'] = summ_df['scroll_down'] - summ_df['page_load']
# Output the user details for the user with the lowest duration
summ_df.sort_values(by = ['duration'])[:1]
如您所见,pivot_table()函数允许我们进行多个聚合,而不必像在 SQL 中那样将它们分离和合并。
日期时间操作
日期时间操作是最常被问到的数据科学面试问题之一。datetime 数据集的普遍性和一系列可以通过简单的数据实现的复杂性使其成为一个流行的数据科学测试领域。Pandas 有许多日期时间函数,涵盖了广泛的日期时间用例。一旦以日期时间格式将数据加载到 Pandas 中,就可以通过调用。dt 访问器。这为我们提供了对各种日期时间方法的访问,这些方法可以通过整个熊猫系列来访问。
让我们在一个真实世界的 Python 熊猫面试问题中使用它。这是一个来自 DoorDash 数据科学的采访。
每个工作日和每个小时的平均收入
报告一周中每天每小时的平均收入数。使用 customer_placed_order_datetime 字段计算相关的日期时间值。收益可视为“订单总额”字段的总和。把星期一当作一周的第一天。
截图来自 StrataScratch
你可以在这里解决问题:https://platform . stratascratch . com/coding/2034-avg-earnings-per-weekday-and-hour?python=1
该问题使用了具有以下字段的 doordash_delivery 数据集。
截图来自 StrataScratch
数据集看起来像这样。
截图来自 StrataScratch
方法和解决方案
为了解决这个 Python 熊猫采访问题,我们需要从相关的 datetime 字段中提取星期几和小时。如问题中所述,该字段为“客户 _ 已下单 _ 订单 _ 日期时间”。
为了获得星期几,我们使用. dt.weekday 属性。根据文档,这将为周一返回 0,为周日返回 6。因为周一我们需要从 1 开始,所以我们在结果上加 1。
# Import your libraries
import pandas as pd
# Keep relevant fields
dd_df = doordash_delivery[['customer_placed_order_datetime', 'order_total']]
# Get the day of the week (add 1 to keep have Monday = 1)
dd_df['weekday'] = dd_df['customer_placed_order_datetime'].dt.weekday + 1
同样,我们也可以提取一天中的小时。为此,我们使用 datetime 对象的. dt.hour 属性。
# Hour of the day
dd_df['hour'] = dd_df['customer_placed_order_datetime'].dt.hour
我们现在可以简单地按一周中的某一天和一天中的某个小时进行聚合,并输出相关的列。
dd_df.groupby(by = ['weekday', 'hour'], as_index = False).agg
({'order_total': 'mean'})
让我们尝试一个稍微难一点的 Python 熊猫面试问题。这一个来自 Salesforce Data Science 访谈,使用日期时间操作和数据透视表。
用户增长率
计算每个账户在 2021 年 1 月和 2020 年 12 月的活跃用户增长率。
截图来自 StrataScratch
你可以在这里解决问题。https://platform . stratascratch . com/coding/2052-user-growth-rate?python=1
该问题使用了包含以下各列的 sf_events 数据集。
数据是这样的。
截图来自 StrataScratch
方法和解决方案
我们需要汇总每个帐户在两个不同时间段的用户数量。这是数据透视表的一个完美案例。但是在我们这样做之前,我们首先从日期中提取年和月,因为我们必须对几个月进行汇总。为此,我们使用。strftime()方法。这扩展了 Python 日期时间库中可用的 strftime()方法。这非常类似于我们在 SQL Datetime 文章中讨论过的 SQL 中的 TO_CHAR()函数。
# Import your libraries
import pandas as pd
# Create the Year - Month indicator
sf_events['month'] = sf_events['date'].dt.strftime('%Y-%m')
我们现在可以使用数据透视表对 2020 年 12 月和 2021 年 1 月进行汇总,计算增长率并输出相关列。
# Aggregate relevant months
summ_df = sf_events[sf_events['month'].isin(['2020-12', '2021-01'])].
pivot_table(
index = 'account_id', columns = 'month', values = 'user_id', aggfunc =
'nunique').reset_index()
# Calculate growth rate and output relevant columns
summ_df['growth_rate'] = summ_df['2021-01'] / summ_df['2020-12']
summ_df[['account_id', 'growth_rate']]
文本操作
与 datetime 函数一样,Pandas 提供了一系列字符串函数。就像。对于日期时间函数,我们可以使用。str 访问器在整个系列中使用标准字符串函数。除了标准的字符串库之外,还有一些额外的函数可以派上用场。让我们来看几个 Python 熊猫面试问题的例子。第一个是来自洛杉矶一个城市的数据科学采访。
“面包店”自有设施
截图来自 StrataScratch
你可以在这里解决问题https://platform . stratascratch . com/coding/9697-bakery-owned-facilities?python=1
该问题使用具有以下字段的洛杉矶餐厅健康检查数据集。
截图来自 StrataScratch
数据如下所示。
截图来自 StrataScratch
方法和解决方案
虽然数据集中有许多列,但相关的是 owner_name 和 pe_description。我们首先只保留数据集中的相关列,删除重复的列(如果有的话)。
然后,我们在 owner_name 字段中搜索文本 BAKERY,在 pe_description 字段中搜索低风险。为此,我们使用 str.lower()方法将所有值转换为小写,并使用. str.find()方法查找相关文本的实例。 .str.find() 是 Python 内置方法 find()对字符串类型变量的扩展。
然后,我们使用布尔掩码输出满足这两个标准的行。
# Import your libraries
import pandas as pd
# Keep relevant fields
rel_df = los_angeles_restaurant_health_inspections
[['owner_name', 'pe_description']].drop_duplicates()
# Find the relevant text in the two fields.
rel_df[(rel_df['owner_name'].str.lower().str.find('bakery') >= 0) &( rel_df
['pe_description'].str.lower().str.find('low risk') >=0)]
除了通常的字符串方法之外。str 访问器还有一些额外的方法。一种这样方法是 explode()。
顾名思义,该方法拆分数据帧中的系列或特定列。其他值(如果是数据帧)和索引是重复的。让我们看看这在实践中是如何使用的。我们在本文前面解决的 AirBnB 数据科学面试问题中使用了这一点。
生活设施最多的城市
从 Airbnb 上给定的房产搜索数据集中,找出主人所有房产中设施最多的城市。假设每行代表一个唯一的主机。输出城市名称作为您的解决方案。
截图来自 StrataScratch
airbnb_search_details 数据集中的相关字段是便利设施和城市
截图来自 StrataScratch
方法和解决方案
我们从保留数据集中的相关字段开始(这在实际解决方案中是不需要的。我们这样做是为了使解决方案更容易理解)。
# Import your libraries
import pandas as pd
# Keep Relevant fields
rel_df = airbnb_search_details[['amenities', 'city']]
我们通过调用 split 方法将便利设施字符串拆分成一个列表。
# Split the amenities string
rel_df['amenities'] = rel_df['amenities'].str.split(",")
现在我们调用便利设施列上的 explode()方法。
rel_df = rel_df.explode('amenities')
截图来自 StrataScratch
可以看到,explode 方法为一个 iterable 内部的每个对象创建了一个单独的行,比如 list、set、tuple 等。现在,我们可以聚合城市中的便利设施,并像前面一样输出结果。
# Summaroze by city
rel_df.groupby(by = ['city'], as_index = False).agg({'amenities' : 'count'}).
sort_values(by = ['amenities'], ascending = False).reset_index()['city'][0]
explode 是一个非常强大的函数,对于基于文本操作的问题来说非常方便。
使用熊猫的统计
考虑到处理表格数据的能力,Pandas 也是统计操作的自然选择。虽然 NumPy 被认为是统计操作的首选库,但由于 Pandas 是基于 NumPy 构建的,它继承了相当多的统计度量,可以很容易地调用这些度量来计算这些度量。让我们看几个例子。第一个是来自洛杉矶一个城市的数据科学面试问题。
求 A 级分数的方差和标准差
截图来自 StrataScratch
你可以在这里解决这个 Python 熊猫面试问题。https://platform . stratascratch . com/coding/9708-find-the-variance-and-the-standard-deviation-of-scores-that-have a grade?python=1
这个问题使用了我们之前看到的洛杉矶餐厅健康检查数据集。该数据集具有以下字段。
截图来自 StrataScratch
数据是这样显示的。
截图来自 StrataScratch
方法和解决方案
让我们自己通过计算来解决这个问题。然后我们将通过调用内置的 Pandas 方法来解决这个问题。我们首先对相关字段进行子集划分,只保留那些与 a 级相对应的分数。
# Import your libraries
import pandas as pd
# Subset relevant rows
la_df = los_angeles_restaurant_health_inspections[los_angeles_restaurant_health_
inspections['grade'] == 'A'][['grade', 'score']]
la_df
截图来自 StrataScratch
方差是平均值的均方差。人口方差被定义为
标准差是方差的平方根
我们可以通过计算等级平均值的平方差的平均值来计算方差。我们可以通过使用向量化操作一步完成。根据方差,我们可以很容易地计算出标准差。最后以期望的格式输出结果。
variance = ((la_df['score'] - la_df['score'].mean())**2).mean()
stdev = variance ** 0.5
output_df = pd.DataFrame({'variance' : [variance], 'stdev' : [stdev]})
除了从公式中计算方差,我们还可以调用内置的 Pandas 统计方法。由于我们正在计算人口方差和标准差,我们需要指定熊猫不使用贝塞尔校正。我们可以通过在方差和标准差计算中将 ddof 参数设置为 0 来做到这一点。
variance = la_df['score'].var(ddof = 0)
stdev = la_df['score'].std(ddof = 0)
output_df = pd.DataFrame({'variance' : [variance], 'stdev' : [stdev]})
让我们试试稍微复杂一点的。这是来自谷歌数据科学的采访。
邮件与活动时间的关联
找出一个用户收到的邮件数量和每天总运动量之间的相关性。每天的总运动量就是每天的用户会话数。
截图来自 StrataScratch
这个问题使用了两个数据集
截图来自 StrataScratch
数据显示如下:
google_gmail_emails
截图来自 StrataScratch
google_fit_location
截图来自 StrataScratch
方法和解决方案
我们首先计算每天发送给每个用户的电子邮件数量。我们通过按用户 id 和日期合计电子邮件数量来做到这一点
# Import your libraries
import pandas as pd
# Get the number of emails per day
mail_df = google_gmail_emails.groupby(by = ['to_user', 'day'],
as_index = False).agg({'id' : 'count'}).fillna(0)
mail_df
我们得到下面的数据集。
截图来自 StrataScratch
我们对其他数据集做同样的事情,计算每天的用户会话数。注意:我们只需要对每个用户会话计数一次。
exer_df = google_fit_location.groupby(by = ['user_id', 'day'], as_index =
False).agg({'session_id' : 'nunique'}).rename(columns =
{'user_id': 'to_user'}).fillna(0)
exer_df
给了我们
截图来自 StrataScratch
我们现在合并用户和日期的两个数据集
merged_df = pd.merge(mail_df, exer_df, on = ['to_user', 'day'], how = 'inner')
merged_df
截图来自 StrataScratch
我们现在可以使用内置的协方差函数来计算协方差。协方差输出将提供两个变量的协方差。例如,两个变量 x 和 y 的协方差输出将包含与此类似的内容。
我们可能需要对角线上的值(用绿色突出显示)。因此,我们对相关字段进行子集划分。
merged_df[['id', 'session_id']].corr()[:1]['session_id']
偏移数据
另一个常见的商业案例,尤其是时间序列数据,是找出它们的前一个或后一个值。Pandas 有能力支持这些 SQL 风格的滞后和超前操作。让我们在实践中使用这些方法,解决优步数据科学采访中的一个问题。
逐年流失
计算每年的司机流失率,并报告与前一年相比,人数是增加了还是减少了。
截图来自 StrataScratch
你可以在这里解决所有的问题。https://platform . stratascratch . com/coding/10017-同比-流失?python=1
该问题使用了具有以下字段的 lyft_drivers 数据集。
数据集看起来像这样。
截图来自 StrataScratch
方法和解决方案
我们从计算每年的流失率开始。为此,我们首先从 end_date 字段计算客户流失的年份,然后计算每年的客户流失数量。
# Import your libraries
import pandas as pd
# Get the year from exit date
lyft_drivers['year'] = lyft_drivers['end_date'].dt.year
# Get the number of the drivers churned for each year
summ_df = lyft_drivers.groupby(by = ['year'], as_index = False).agg
({'index' : 'count'}).sort_values(by = ['year']).rename
(columns = {'index' : 'churn'}).dropna()
这为我们提供了以下汇总数据。
截图来自 StrataScratch
检查与前一年相比,该数字是增加了还是减少了。为此,我们需要偏移或下移客户流失数量的值。我们可以通过使用 shift()方法来实现这一点。顾名思义,shift 方法将数据偏移 n 行。也可以传递负数,以便向上移动数值。
# Fetch the prev year's churn numbers
summ_df['prev_churn'] = summ_df['churn'].shift(1).fillna(0)
summ_df
截图来自 StrataScratch
我们可以将该值与前一项进行比较,并确定数字是增加了还是减少了。
# Compare the two churn numbers and output the change
summ_df['change'] = (summ_df['churn'] > summ_df['prev_churn']).apply
(lambda x : 'increase' if x == True else 'decrease')
summ_df
额外的 Python 熊猫面试问题
作者在 Canva 上创建的图像
我们最后解决了几个问题,这些问题结合了我们所学的所有知识。第一种使用窗口函数。这是来自亚马逊数据科学的采访。
收入随时间变化
求每个月的三个月移动平均线。
截图来自 StrataScratch
你可以在这里解决这个 Python 熊猫面试问题https://platform . stratascratch . com/coding/10314-收入-时间?python=1
该问题使用了具有以下字段的 amazon_purchases 数据集。
数据是这样的。
截图来自 StrataScratch
方法和解决方案
我们从相关事务的数据子集开始。我们删除问题中描述的退款交易。我们通过调用 created_date 字段上的 strftime 方法找到所需的月份指示器。我们进一步汇总每个月的购买交易。
# Import your libraries
import pandas as pd
# Remove refund transactions
pos_df = amazon_purchases[amazon_purchases['purchase_amt'] > 0]
# Create Month indicator
pos_df['month'] = pos_df['created_at'].dt.strftime("%Y-%m")
# Aggregate the purchases by month
summ_df = pos_df.groupby(by = ['month'], as_index = False).sum()
[['month', 'purchase_amt']].sort_values(by = ['month'])
截图来自 StrataScratch
为了找到移动平均线,我们使用滚动函数。滚动函数创建 n 行的移动窗口。我们可以改变参数来得到我们想要的输出。因为我们不希望前两个观察返回空值,所以我们将 min_periods 参数设置为 1。最后,我们调用 mean()方法来计算三个月的平均值,并返回相关字段。
# Calculate the rolling average, ensure that the value is calculated
even for the first two months
summ_df['roll_avg'] = summ_df['purchase_amt'].rolling
(3, min_periods = 1).mean()
# Output relevant fields
summ_df[['month', 'roll_avg']]
下一个来自亚马逊数据科学访谈,以创新的方式使用了 apply 方法。
连胜最久的选手
连胜是特定玩家赢得的一系列连续比赛。当一名球员输掉下一场比赛时,连胜就结束了。输出具有最长连胜的玩家的 ID 和连胜的长度。
截图来自 StrataScratch
你可以在这里解决这个 Python 熊猫面试问题。https://platform . stratascratch . com/coding/2059-连胜最长的球员?python=1
这个熊猫面试问题使用带有以下字段的玩家 _ 结果数据集。
数据是这样显示的。
截图来自 StrataScratch
方法和解决方案
为了解决这个问题,我们需要得到一个玩家的结果。假设一个玩家的结果序列是
WWLWLWWWLWWWWWWWLLLWLW
我们可以简单地通过使用字母“L”作为分隔符来拆分字符串。这将把字符串分成几个列表。类似这样的。
[WW] [W] [WWW] [WWWWWWW] [] [] [] [W] [W].
最后,我们找到最长列表的长度,我们将能够确定玩家的连胜。为了在熊猫身上做到这一点,我们需要连接结果。为此,我们只需应用 sum()方法。
当传递数字数据时,sum()方法应该给出值的总数。但是当字符串类型数据被传递时,就会执行加法运算。Python 中的加法运算符将连接字符串,这正是我们需要的!!。
我们从连接结果开始。
# Import your libraries
import pandas as pd
# Create the sequence of results
streak_df = players_results.groupby(by = ['player_id'], as_index = False).agg
({'match_result': 'sum'})
截图来自 StrataScratch
然后,我们继续使用字母 L 作为分隔符来拆分 match_result 字符串。我们还分解了结果,这样每个列表都是单独的一行。
# Split the sequence using 'L' as the separator and explode
streak_df['streak'] = streak_df['match_result'].str.split('L')
streak_df = streak_df.explode(column = 'streak')
截图来自 StrataScratch
现在剩下的问题就简单了。我们只需要合计条纹字符串的最大长度,并输出相关字段。
# Find the length of the streak
streak_df['streak_len'] = streak_df['streak'].apply(len)
# Aggregate
streaks_df = streak_df.groupby(by = ['player_id'], as_index = False).agg
({'streak_len' : 'max'})
# Output relevant fields
streaks_df['rank'] = streaks_df['streak_len'].rank(method =
'dense', ascending = False)
streaks_df[streaks_df['rank'] == 1].drop(columns = ['rank'])
结论
在这一系列文章中,我们看了如何使用熊猫和如何解决 Python 熊猫面试问题。如果一个人真的想从事以 Python 为主要工具的数据科学领域的工作,那么他应该精通熊猫。使用 Pandas 就像使用 MS-Excel、Google Sheets、Numbers 或 LibreOffice Calc 等电子表格软件一样简单。一个人要精通熊猫只需要一点时间和好奇心。我们在 StrataScratch 平台上有超过 700 个与数据科学面试相关的编码和非编码问题。这些问题来源于优步、网飞、Noom、微软、脸书等顶级公司的实际数据科学面试。查看我们最近关于 前 30 名 Python 面试问题和答案 的帖子。在 StrataScratch 上,您将有机会加入一个由 20,000 多名志同道合的数据科学爱好者组成的团体,获得协作学习体验。今天就在 StrataScratch 上注册,为全球大型科技公司和初创公司最受欢迎的职位做好准备。
原载于https://www.stratascratch.com。
Python 熊猫阅读 CSV
学习如何阅读 CSV 文件并创建熊猫数据帧
介绍
作为数据分析师或数据科学家,您将经常需要组合和分析来自各种数据源的数据。我经常被要求分析的一种数据类型是 CSV 文件。CSV 文件在企业界很受欢迎,因为它们可以处理强大的计算,易于使用,并且通常是企业系统的输出类型。今天我们将演示如何使用 Python 和 Pandas 打开并读取本地机器上的 CSV 文件。
入门指南
你可以通过 pip 从 PyPI 安装 Panda。如果这是你第一次安装 Python 包,请参考 Pandas 系列&数据帧讲解或 Python Pandas 迭代数据帧。这两篇文章都将为您提供安装说明和今天文章的背景知识。
句法
在学习熊猫时,对我来说最具挑战性的部分是关于熊猫功能的大量教程,比如.read_csv()
。然而,教程往往忽略了处理真实世界数据时所需的复杂性。一开始,我经常发现自己不得不在 StackOverflow 上发布问题,以学习如何应用特定的参数。下面我们包括了所有的参数,以及概念上更复杂的例子。
上面的 python 片段显示了 Pandas read csv 函数的语法。
上面的语法乍一看似乎很复杂;然而,我们将只设置少数几个参数,因为大多数参数都被赋予了默认值。然而,这些参数预示着熊猫强大而灵活的天性。
因素
filepath_or_buffer
:您可以传入一个字符串或路径对象,该对象引用您想要读取的 CSV 文件。该参数还接受指向远程服务器上某个位置的 URL。
上面的 Python 片段展示了如何通过向 filepath_or_buffer 参数提供文件路径来读取 CSV。
sep
&delimiter
:delimiter
参数是sep
的别名。你可以使用sep
来告诉熊猫使用什么作为分隔符,默认情况下这是,
。但是,您可以为制表符分隔的数据传入 regex,如\t
。header
:该参数允许您传递一个整数,该整数捕获 CSV 头名称所在的行。默认情况下,header
被设置为infer
,这意味着 Pandas 将从第 0 行获取标题。如果您打算重命名默认标题,则将header
设置为0
。name
:这里您有机会覆盖默认的列标题。为此,首先设置header=0
,然后传入一个数组,该数组包含您想要使用的新列名。
上面的 Python 片段重命名了原始 CSV 的头。我们在上面的例子中使用的 CSV 的副本可以在这里找到。
index_col
:对于不熟悉 DataFrame 对象的人来说,DataFrame 的每一行都有一个标签,称为索引。如果 CSV 文件包含表示索引的列,则可以传递列名或整数。或者,您可以通过False
告诉 Pandas 不要使用您的文件中的索引。如果False
通过,Pandas 将使用一系列递增的整数创建一个索引。usecols
:您可以使用该参数返回文件中所有列的子集。默认情况下,usecols
被设置为None
,这将导致 Pandas 返回 DataFrame 中的所有列。当您只对处理某些列感兴趣时,这很方便。
在上面的 Python 片段中,我们告诉 Pandas 我们只希望读取第 1 和第 2 列。您可以使用这个 CSV 来测试上面的代码片段。
squeeze
:当处理一个单列 CSV 文件时,您可以将该参数设置为True
,这将告诉 Pandas 返回一个系列,而不是一个数据帧。如果对熊猫系列不熟悉,可以参考熊猫系列& DataFrame 讲解进行概述。prefix
:如果你还没有指定要使用的列标签前缀,你可以在这里设置。当没有指定列时,默认行为是使用整数序列来标记它们。使用该参数,您可以将列0
、1
和2
设置为column_0
、column_1
和column_2
。
在上面的 Python 片段中,我们试图读取一个没有头文件的 CSV 文件。默认情况下,熊猫会添加数字列标签。在上面,我们已经用“column_”作为列的前缀。我们在这个例子中使用的 CSV 可以在这里找到。
mangle_dupe_cols
:如果您正在阅读的 CSV 文件包含同名的列,Pandas 将为每个重复的列添加一个整数后缀。将来mangle_dupe_cols
将接受False
,这将导致重复的列相互覆盖。dtype
:您可以使用这个参数来传递一个字典,这个字典将列名作为键,数据类型作为它们的值。当您有一个以零填充的整数开头的 CSV 时,我发现这很方便。为每一列设置正确的数据类型也将提高操作数据帧时的整体效率。
上面我们已经确保了 employee_no 列将被转换为一个字符串。这还演示了保留前导零的能力,因为您会注意到数据集中的 job_no 没有被转换,因此丢失了前导零。数据集可以在这里找到。
engine
:目前熊猫接受c
或python
作为解析引擎。converters
:这遵循与dtype
相似的逻辑,但是,除了传递数据类型,您还可以传递在读取时操作特定列中的值的函数。
上面的 Python 代码片段将应用 double_number()函数,我们将它定义为第 1 列的转换器。我们的示例 CSV 文件可以在这里找到。
true_values
&false_values
:这个参数挺俏皮的。例如,在您的 CSV 中,您有一个包含yes
和no
的列,您可以将这些值映射到True
和False
。这样做将允许您在将文件读入 Pandas 时清除一些数据。
上面的 Python 片段演示了如何定义 True & False 值。这里我们将 yes & maybe 设为 True,no 设为 False。此处可找到一个 CSV 示例。
skipinitialspace
:您可以将此参数设置为True
,告诉熊猫分隔符后可能有前导空格的行。然后,Pandas 将删除分隔符之后和非分隔符之前的任何前导空格。skiprows
:在处理系统生成的 CSV 文件时,有时文件的开头会包含参数行。通常我们不想处理这些行,而是跳过它们。您可以将skiprows
设置为一个整数,表示在开始读取之前要跳过的行数。或者,您可以提供一个 callable,当函数计算结果为True
时,它将导致 Pandas 跳过一行。skipfooter
:类似于skiprows
这个参数允许你告诉熊猫在文件的末尾要跳过多少行。同样,如果报告参数在 CSV 文件的末尾,这也很方便。nrows
:您可以使用它来设置从 CSV 文件中收集的行数的限制。我发现在探索阶段,当试图对数据有所感觉时,这很方便。这意味着您可以测试您的逻辑,而不必将大文件加载到内存中。na_values
:默认情况下,Pandas 有大量的值被映射到NaN
(不是一个数字)。如果您有需要清理和映射的特定于应用程序的值,可以将它们传递给此参数。使用这个参数意味着您可以捕获所有的值,这些值都可以映射到一个默认的预处理。keep_default_na
:该参数可以设置为True
或False
。如果False
和 CSV 包含默认的NaN
值,那么 Pandas 将保留原来的NaN
值。如果True
Pandas 将解析NaN
值并在数据帧中用NaN
屏蔽。na_filter
:当您希望 Pandas 解释您的数据中缺失的值时,您可以将此设置为True
。提示一下,当读取您知道没有任何丢失值的大文件时,将此参数设置为False
。verbose
:默认设置为False
。将verbose
设置为True
将向控制台输出额外的数据,如NaN
值的数量或特定过程花费的时间。- 有时,我们收到的数据可能包含空行。通过将
skip_blank_lines
设置为True
,Pandas 将跳过这些行,而不是将它们计为NaN
值。 parse_dates
:使用这个参数告诉 Pandas 你希望 CSV 文件中的日期如何被解释。你可以通过True
,这会让熊猫把索引解析成日期。或者,您可以传递 Pandas 将用来创建日期的列名或列列表。
上面的 Python 代码片段将第 0 列转换成索引,并将其解析为日期。用于本例的 CSV 文件可以在这里找到。运行上述脚本时,请注意针对日期时间格式对列 0 所做的更改。
infer_datetime_format
:您可以将此参数设置为True
,它将告诉熊猫推断日期时间格式。当与parse_dates
结合时,这样做将导致更大的处理速度。keep_date_col
:如果您已经为parse_dates
设置了一个值,您可以使用该参数来保留创建数据的列。默认行为是将这些列放到适当的位置。如果您不希望这种情况发生,请将keep_date_col
设置为True
。
上面的 Python 脚本将通过尝试解析列 0、1 和 2 来创建一个日期。此外,keep_date_col 已被设置为 True,这将导致保留列 0、1 和 2。我们的示例 CSV 可以在这里找到。
date_parser
:如果您已经知道 CSV 中的日期格式,您可以传递一个函数给date_parser
来有效地格式化日期时间,而不是推断格式。dayfirst
:如果你的日期时间格式是DD/MM
,则通过True
。cache_dates
:默认设置为True
。Pandas 将创建一组独特的日期-时间字符串转换,以加快重复字符串的转换。iterator
:将该参数设置为True
将允许您调用 Pandas 函数.get_chunk()
,该函数将返回要处理的记录数。chunksize
:这将允许您设置数据帧内块的大小。这样做很方便,因为您可以循环数据帧的一部分,而不是将整个数据帧延迟加载到内存中。compression
:如果您正在读取的数据在磁盘上被压缩,那么您可以设置动态解压缩的压缩类型。thousands
:这是千位单位的分隔符。在 CSV 文件中,你有时可以看到用1_000_000
表示的一百万,因为,
被用作分隔符。将千设置为_
将导致1_000_000
反映为1000000
。decimal
:如果偏离.
,您可以在 CSV 文件中提供代表小数的字符。lineterminator
:如果你已经将engine
设置为c
,你可以用这个参数告诉熊猫你希望这些行以什么字符结束。quotechar
:这是在整个 CSV 文件中使用的字符,表示引用元素的开始和结束。quoting
:在这里你可以设置你想要应用到你的元素的报价级别。默认情况下,这是 0,将报价设置为最小;您也可以将其设置为 1-全部引用、2-引用非数字或 3-不引用。doublequote
:当两个引号字符出现在一个引号元素中时,您可以使用这个参数告诉 Pandas 该做什么。当True
通过时,双引号字符将变成单引号字符。escapechar
:长度为一的字符串,熊猫将使用它来转义其他字符。comment
:您可以使用该参数来表示您不想处理该行的剩余部分。例如,如果comment
设置为#
并且#
出现在当前行中,熊猫到达#
后将移动到下一行。encoding
:如果您正在使用非英语的数据,请将此值设置为特定的字符编码,以便可以正确读取数据。dialect
:CSV 方言是一组告诉 CSV 解析器如何读取 CSV 文件的参数。常见的方言有excel
、excel-tab
和unix
另外,你可以自己创作,传给熊猫。error_bad_lines
:如果 Pandas 遇到一个具有两个以上属性的行,通常会引发一个异常,Python 会暂停执行。如果将False
传递给error_bad_lines
,那么任何通常会引发此类异常的行将从数据帧中删除。warn_bad_lines
:如果您已经将error_bad_lines
设置为False
,那么您可以将warn_bad_lines
设置为True
,这将输出每一行会引发异常的代码。delim_whitespace
:这个参数与delimiter
相似,但是它只针对空白。如果你想用空格作为分隔符,那么你可以将delimiter
设置为\s+
,或者将delim_whitespace
设置为True
。low_memory
:默认情况下,Pandas 将此设置为True
,这将导致分块处理,然而,存在不匹配类型推理的风险。通过确保设置了dtype
参数,可以避免可能的类型不匹配。memory_map
:如果你已经传递了一个文件给filepath_or_buffer
Pandas 在内存中映射文件对象,以提高处理较大文件的效率。float_precision
:您可以在这里为浮动元素的c
引擎设置合适的转换器。storage_options
:从远程位置读取 CSV 文件时,可以使用该参数传递特定选项。
接下来去哪里
既然您已经了解了如何使用 Pandas .read_csv()
,我们的建议是通过 Pandas 系列& DataFrame 解释来学习更多关于 Pandas 数据结构的知识,或者在 Python Pandas 迭代 DataFrame 中学习如何导航数据帧。如果你已经掌握了这些概念,你的下一步应该是阅读旋转熊猫数据框架或如何组合 Python、熊猫& XlsxWriter 。
摘要
作为一名数据分析师,学习如何使用 Pandas .read_csv()
是一项至关重要的技能,可以将各种数据源结合起来。正如你在上面看到的,.read_csv()
是一个非常强大和灵活的工具,你可以适应各种现实世界的情况来开始你的数据收集和分析。
感谢您花时间阅读我们的故事,我们希望您发现它很有价值。
Python 熊猫 vs. R Dplyr
完整的备忘单
图片由作者根据https://allisonhorst.github.io/的 Dplyr 标志和https://pandas.pydata.org/about/citing.html的熊猫标志制作
期待什么
对于许多数据科学家来说, Pandas for Python 和 Dplyr for R 是两个最流行的处理表格/结构化数据的库。关于哪个框架更好,总会有一些激烈的讨论。老实说,这真的重要吗?最后,这是关于完成工作,熊猫和 dplyr 都提供了很好的数据争论工具。**不用担心,这篇文章并不是另一个试图证明任何一个库的观点的比较!**因此,本条的目的是:
- 帮助其他人从一种语言/框架过渡到另一种语言/框架
- 探索可以作为数据科学家增加技能的新工具
- 创建一个参考备忘单,以防您需要查找两种语言中最常用的数据争论函数
数据
在本教程中,我们将使用 iris 数据集,它是 Pythons sklearn 和 base R 的一部分。
Sepal_length Sepal_width Petal_length Petal_width Species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5.0 3.6 1.4 0.2 setosa
小抄
免责声明:长阅读
选择列
这里已经变得令人困惑了。首先,在每个框架中,如何从数据框架中选择列有多种方法。在 Pandas 中,您可以简单地传递带有列名的列表,或者使用 filter() 方法。这令人困惑,因为 dplyr 中的 filter() 函数用于根据条件而不是根据列对行进行子集化!在 dplyr 中,我们使用 select() 函数来代替:
熊猫
#Pass columns as list
dataframe[[“Sepal_width”, “Petal_width”]] #Use Filter Function
dataframe.filter(items=['Sepal_width', 'Petal_width'])
Dplyr
dataframe %>% select(Sepal_width, Petal_width)
基于条件的过滤
同样,有多种方法可以根据一列或多列的条件过滤数据帧中的记录。
熊猫
在 Pandas 中,你可以使用索引方法或者尝试方便的查询 API,这是我个人更喜欢的。
#indexing
dataframe[(dataframe["Sepal_width"] > 3.5) & (dataframe["Petal_width"] < 0.3)] #query API
dataframe.query("Sepal_width > 3.5 & Petal_width < 0.3")
Dplyr
在 dplyr 中过滤记录的标准方式是通过 filter 函数()。
dataframe %>% filter(Sepal_width > 3.5 & Petal_width < 0.3)
重命名单列
重命名听起来像是一个简单的任务,但是要小心,注意这里的细微差别。如果我们想在熊猫中将我们的列从物种重命名为类,我们提供一个字典,上面写着**{‘物种’:‘类’},而在 Dplyr 中正好相反类=物种**:
熊猫
dataframe.rename(columns = {'Species': 'Class'}, inplace = True)
Dplyr
dataframe <- dataframe %>% rename(Class=Species)
根据条件重命名多个列
假设我们想根据一个条件一次重命名多个列。例如,将我们所有的特征列(萼片长度、萼片宽度、花瓣长度、花瓣宽度)转换为大写。在 Python 中,这实际上相当复杂,您需要首先导入另一个库,并手动迭代每一列。在 Dplyr 中,如果您想根据条件访问/更改多个列,有一个更简洁的界面。
熊猫
import re#prepare pattern that columns have to match to be converted to upper case
pattern = re.compile(r".*(length|width)")#iterate over columns and covert to upper case if pattern matches.
for col in dataframe.columns:
if bool((pattern.match(col))):
dataframe.rename(columns = {col: col.upper()}, inplace = True)
Dplyr
dataframe <- dataframe %>% rename_with(toupper, matches("length|width"))
结果
请注意大写特征列名:
SEPAL_LENGTH SEPAL_WIDTH PETAL_LENGTH PETAL_WIDTH Species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
根据条件改变单元格值
假设我们想要基于条件重新编码/改变单元格值:在我们的示例中,我们将尝试将物种字符串“setosa,versicolor 和 virginica”重新编码为从 0 到 2 的整数:
熊猫
dataframe.loc[dataframe['Species'] == 'setosa', "Species"] = 0
dataframe.loc[dataframe['Species'] == 'versicolor', "Species"] = 1
dataframe.loc[dataframe['Species'] == 'virginica', "Species"] = 2
Dplyr
dataframe <- dataframe %>%
mutate(Species = case_when(Species == 'setosa' ~ 0,
Species == 'versicolor' ~ 1,
Species == 'virginica' ~ 2))
每列的不同值
有时我们想看看一列中有哪些不同的/唯一的值。请注意这两个框架中的函数调用有多么不同:Pandas 使用 unique() 方法,dplyr()使用 distinct() 函数来获得相同的结果:
熊猫
dataframe.Species.unique()#array(['setosa', 'versicolor', 'virginica'], dtype=object)
Dplyr
dataframe %>% select(Species) %>% distinct()# Species
# setosa
# versicolor
# virginica
记录计数(每组)
如果您想计算数据帧中总共有多少个条目,或者获取某个组的计数,您可以执行以下操作:
熊猫
# Total number of records in dataframe
len(dataframe)#150 # Number of records per Group
dataframe.value_counts('Species')#Species
#virginica 50
#versicolor 50
#setosa 50# Note that you can also use the .groupby() method followed by size()
dataframe.groupby(['Species']).size()
Dplyr
# Total number of records in dataframe
dataframe %>% nrow()#[1] 150 # Number of records per Group (count and tally are interchangeable)
dataframe %>% group_by(Species) %>% count()
dataframe %>% group_by(Species) %>% tally()# Species n
# <fct> <int>
#1 setosa 50
#2 versicolor 50
#3 virginica 50
对整个列进行汇总/聚合
如果要为数据框中的一列或多列创建描述性统计数据,可执行以下操作:
熊猫
#get mean and min for each column
dataframe.agg(['mean', 'min'])# Sepal_length Sepal_width Petal_length Petal_width Species
#mean 5.843333 3.057333 3.758 1.199333 NaN
#min 4.300000 2.000000 1.000 0.100000 setosa
Dplyr
不幸的是,我没有找到如何一次在多个列上使用多个聚合函数的方法。这就是为什么您需要多次调用 summarise 函数来获得相同的结果:
#first aggregation over all columns using mean
dataframe %>% summarise(across(everything(), mean))# Sepal_length Sepal_width Petal_length Petal_width Species
# 5.84 3.06 3.76 1.20 NA#second aggregation over all columns using min
dataframe %>% summarise(across(everything(), min))#Sepal_length Sepal_width Petal_length Petal_width Species
# 5.84 3.06 3.76 1.20 NA
按组汇总/汇总
如果想要在数据集中按组聚集统计数据,必须使用 Pandas 中的 groupby() 方法和 Dplyr 中的 group_by() 函数。您可以对所有列或特定列执行此操作:
熊猫
请注意 Pandas 如何使用多级索引来清晰地显示结果:
# aggregation by group for all columns
dataframe.groupby(['Species']).agg(['mean', 'min'])# Sepal_length Sepal_width ...
# mean min mean min ...
#Species
#setosa 5.01 4.3 3.43 ...
#versicolor 5.94 4.9 2.77 ...
#virginica 6.59 4.9 2.97 ... # aggregation by group for a specific column
dataframe.groupby(['Species']).agg({'Sepal_length':['mean']})# Sepal_length
# mean
#Species
#setosa 5.01
#versicolor 5.94
#virginica 6.59
Dplyr
由于 Dplyr 不支持多级索引,所以第一次调用的输出与 Pandas 相比看起来有点乱。在此输出中,显示了第一个函数的统计数据(mean-fn1),然后是第二个函数的统计数据(min-fn2)。
# aggregation by group for all columns
dataframe %>% group_by(Species) %>% summarise_all(list(mean,min))Species Sepal_length_fn1 Sepal_width_fn1 ...
setosa 5.01 3.43 ...
versicolor 5.94 2.77 ...
virginica 6.59 2.97 ... # aggregation by group for a specific column
dataframe %>% group_by(Species) %>% summarise(mean=mean(Sepal_length))#Species mean
# setosa 5.01
# versicolor 5.94
# virginica 6.59
列数学/添加新列
有时,您希望创建一个新列,并通过某种数学运算将两个或多个现有列的值组合起来。以下是如何在熊猫和 Dplyr 中做到这一点:
熊猫
dataframe["New_feature"] = dataframe["Petal_width"]* dataframe["Petal_length"] / 2
Dplyr
dataframe <- dataframe %>% mutate(New_feature= Petal_width*Petal_length/2)
删除列
为了清理数据帧,删除列有时会非常方便:
熊猫
在 Pandas 中,你可以用 drop() 删除一列。您也可以使用 inplace=True 来覆盖当前数据帧。
dataframe.drop("New_feature", axis=1, inplace=True)
Dplyr
在 Dplyr 中,您可以在 select()函数中使用前导的减号来指定要删除的列名。
dataframe <- dataframe %>% select(-New_feature)
按值对记录排序
要对值进行排序,您可以在 Pandas 中使用 sort_values() ,在 Dplyr 中使用 arrange() 。两者的默认排序都是升序。请注意每个函数调用在降序排序方面的差异:
熊猫
dataframe.sort_values('Petal_width', ascending=0)
Dplyr
dataframe %>% arrange(desc(Petal_width))
重命名单列
重命名听起来像是一个简单的任务,但是要小心,注意这里的细微差别。如果我们想在 Pandas 中将我们的列从 Species 重命名为 Class ,我们提供一个字典,上面写着 {‘Species’: ‘Class’} ,而在 Dplyr 中,情况正好相反 Class=Species :
熊猫
dataframe.rename(columns = {'Species': 'Class'}, inplace = True)
Dplyr
dataframe %>% relocate(Species)
dataframe %>% relocate(Species, .before=Sepal_width)
更改列的顺序
我不经常使用这个功能,但是如果我想为一个演示创建一个表格,并且列的排序没有逻辑意义,这个功能有时会很方便。以下是如何移动列:
熊猫
在 Python Pandas 中,您需要通过使用列表来重新索引您的列。假设我们想将列物种移到前面。
#change order of columns
dataframe.reindex(['Species','Petal_length','Sepal_length','Sepal_width','Petal_Width'], axis=1)
Dplyr
在 Dplyr 中,您可以使用方便的 relocate() 函数。同样,假设我们想将列物种移到前面。
dataframe %>% relocate(Species)#Note that you can use .before or .after to place a columne before or after another specified column - very handy!
dataframe %>% relocate(Species, .before=SEPAL_WIDTH)
限幅
切片本身就是一个完整的主题,有很多方法可以实现。下面让我们来看一下最常用的切片操作:
按行切片
有时您知道想要提取的确切行号。虽然 Dplyr 和 Pandas 中的过程非常相似**,但是请注意 Python 中的索引从 0 开始,而 R 中的索引从 1 开始。**
熊猫
dataframe.iloc[[49,50]]# Sepal_length Sepal_width Petal_length Petal_width Species
# 5.0 3.3 1.4 0.2 setosa
# 7.0 3.2 4.7 1.4 versicolor
Dplyr
dataframe %>% slice(50,51)# Sepal_length Sepal_width Petal_length Petal_width Species
#1 5 3.3 1.4 0.2 setosa
#2 7 3.2 4.7 1.4 versicolor
分割第一个和最后一个记录(头/尾)
有时我们希望看到数据帧中的第一条或最后一条记录。这可以通过提供一个固定数量 n 或一个比例 prop 值来实现。
熊猫
在 Pandas 中,您可以使用 head() 或 tail() 方法来获取固定数量的记录。如果你想提取一个比例,你必须自己计算一下:
#returns the first 5 records
dataframe.head(n=5)#returns the last 10% of total records
dataframe.tail(n=len(dataframe)*0.1)
Dplyr
在 Dplyr 中,有两个为这个用例指定的函数: slice_head() 和 slice_tail() 。请注意如何指定固定数量或比例:
#returns the first 5 records
dataframe %>% slice_head(n=5)#returns the last 10% of total records
dataframe %>% slice_tail(prop=0.1)
按值对第一条和最后一条记录进行切片
有时,选择每列具有最高或最低值的记录很有用。同样,这可以通过提供固定的数量或比例来实现。
熊猫
对于熊猫来说,这比 Dplyr 更棘手。例如,假设您想要 20 个具有最长“花瓣长度”的记录,或者 10%的总记录具有最短的“花瓣长度”。要在 Python 中进行第二个操作,我们必须做一些数学计算,首先对我们的值进行排序:
#returns 20 records with the longest Petal_length (for returning the shortest you can use the function nsmallest)
dataframe.nlargest(20, 'Petal_length')#returns 10% of total records with the shortest Petal_length
prop = 0.1
dataframe.sort_values('Petal_length', ascending=1).head(int(len(dataframe)*prop))
Dplyr
在 Dplyr 中,这要简单得多,因为此用例有指定的函数:
#returns 20 records with the longest Petal_length
dataframe %>% slice_max(Petal_length, n = 20)#returns 10% of total records with the shortest Petal_lengthdataframe %>% slice_min(Petal_length, prop = 0.1)
按值和组对第一条和最后一条记录进行切片
有时,选择每列具有最高或最低值的记录**,但由组**分隔,这很有用。同样,这可以通过提供固定的数量或比例来实现。想象一下,例如我们想要 3 个每个物种花瓣长度最短的记录。
熊猫
对熊猫来说,这也比 Dplyr 更棘手。我们首先按照物种对数据帧进行分组,然后应用一个 lambda 函数,该函数利用了上述的 nsmallest() 或 nlargest() 函数:
#returns 3 records with the shortest Petal_length per Species
(dataframe.groupby('Species',group_keys=False)
.apply(lambda x: x.nsmallest(3, 'Petal_length')))#Sepal_length Sepal_width Petal_length Petal_width Species
# 4.6 3.6 1.0 0.2 setosa
# 4.3 3.0 1.1 0.1 setosa
# 5.8 4.0 1.2 0.2 setosa
# 5.1 2.5 3.0 1.1 versicolor
# 4.9 2.4 3.3 1.0 versicolor
# 5.0 2.3 3.3 1.0 versicolor
# 4.9 2.5 4.5 1.7 virginica
# 6.2 2.8 4.8 1.8 virginica
# 6.0 3.0 4.8 1.8 virginica#returns 5% of total records with the longest Petal_length per Species
prop = 0.05
(dataframe.groupby('Species',group_keys=False)
.apply(lambda x: x.nlargest(int(len(x) * prop), 'Petal_length')))#Sepal_length Sepal_width Petal_length Petal_width Species
# 4.8 3.4 1.9 0.2 setosa
# 5.1 3.8 1.9 0.4 setosa
# 6.0 2.7 5.1 1.6 versicolor
# 6.7 3.0 5.0 1.7 versicolor
# 7.7 2.6 6.9 2.3 virginica
# 7.7 3.8 6.7 2.2 virginica
Dplyr
在 Dplyr 中,这要简单得多,因为这个用例有指定的函数。注意如何提供 with_ties=FALSE 来指定是否应该返回 ties(具有相等值的记录)。
#returns 3 records with the shortest Petal_length per Species
dataframe %>% group_by(Species) %>% slice_min(Petal_length, n = 3, with_ties = FALSE)#returns 5% of total records with the longest Petal_length per Species
dataframe %>% group_by(Species) %>% slice_max(Petal_length, prop = 0.05, with_ties=FALSE)
切片随机记录(每组)—抽样
对随机记录进行切片也可以称为抽样。这也可以通过证明一个固定的数字或比例来实现。此外,这可以在整个数据集上进行,也可以基于组平均分布。因为这是一个相当频繁的用例,所以在两个框架中都有这样的函数:
熊猫
在 Pandas 中,您可以使用 sample() 函数,或者指定 n 为固定数量的记录,或者指定 frac 为一定比例的记录。此外,您可以指定替换来允许或不允许对同一行进行多次采样。
#returns 20 random samples
dataframe.sample(n=20)#return 20% of total records
dataframe.sample(frac=0.2, replace=True)#returns 10% of total records split by group
dataframe.groupby('Species').sample(frac=0.1)
Dplyr
Dplyr 中的界面非常相似。您可以使用 slice_sample() 函数,或者为固定数量的记录指定 n ,或者为一定比例的记录指定 prop 。此外,您可以指定替换以允许或不允许对同一行进行多次采样。
#returns 20 random samples
dataframe %>% slice_sample(n=20)#return 20% of total records
dataframe %>% slice_sample(prop=0.2, replace=True)#returns 10% of total records split by group
dataframe %>% group_by(Species) %>% slice_sample(prop=0.1)
连接
连接数据框架也是一个常见的用例。(join 操作范围很广,但我不打算在此详述)
但是,随后您将学习如何在 Pandas 和 Dplyr 中执行完全(外部)连接。
图像您有两个共享一个公共变量“key”的数据帧:
#Python Pandas
A = dataframe[[“Species”, “Sepal_width”]]
B = dataframe[[“Species”, “Sepal_length”]]#R Dplyr:
A <- dataframe %>% select(Species, Sepal_width)
B <- dataframe %>% select(Species, Sepal_length)
熊猫
对于所有的加入操作,你可以使用 Pandas 中的“合并”功能,并指定你想加入什么,如何加入(外部,内部,左侧,右侧,…)您想加入哪个键:
#Join dataframe A and B (WHAT), with a full join (HOW) by making use of the key "Species" (ON)
pd.merge(A, B, how="outer", on="Species")
Dplyr
在 Dplyr 中,语法非常相似,但是,对于每种连接类型,都有单独的函数。在本例中,我们将再次使用 full_join() 函数执行完全连接:
#Join dataframe A and B (WHAT), with a full join (HOW) by making use of the key "Species" (ON)
A %>% full_join(B, by="Species")
连接/绑定行和列
有时我们不想连接我们的数据帧,而只是通过行或列附加两个现有的数据帧。熊猫和 Dplyr 都有一个很好的界面来实现这一点:
熊猫
在 Pandas 中,可以用 concat() 方法连接两个数据帧。默认值按行连接数据帧。通过指定轴(例如轴= 1),可以通过列连接两个数据帧。
请注意,如果某个值没有出现在其中一个数据帧中,它会自动填充 NA。
#Concatenate by rows
pd.concat([A,B])# Species Sepal_width Sepal_length
#0 setosa 3.5 NaN
#1 setosa 3.0 NaN
#2 setosa 3.2 NaN
#3 setosa 3.1 NaN
# ... #Concatenate by columns
pd.concat([A,B], axis=1)# Species Sepal_width Species Sepal_length
#0 setosa 3.5 setosa 5.1
#1 setosa 3.0 setosa 4.9
#2 setosa 3.2 setosa 4.7
#3 setosa 3.1 setosa 4.6
# ...
Dplyr
在 Dplyr 中,有两个独立的函数用于绑定数据帧: bind_rows() 和 bind_columns()。
请注意,如果某个值没有出现在其中一个数据帧中,那么在应用 bind_rows()时,它会自动填充 NA。另外,请注意 R 如何自动更改列名(以避免重复)。使用可以改变这种行为。name_repair 参数。
#Bind by rows
A %>% bind_rows(B)# Species Sepal_width Sepal_length
# 1 setosa 3.5 NA
# 2 setosa 3 NA
# 3 setosa 3.2 NA
# 4 setosa 3.1 NA
# ...#Bind by columns
A %>% bind_cols(B)# Species...1 Sepal_width Species...3 Sepal_length
# 1 setosa 3.5 setosa 5.1
# 2 setosa 3 setosa 4.9
# 3 setosa 3.2 setosa 4.7
Pfew!恭喜你!你可能是第一个读到这篇文章/备忘单结尾的人。您可以通过给我鼓掌或在下面给我留言来获得奖励:)
图片来自 Giphy
Python 并行性:几分钟内加速 Python 代码的基本指南
Python 多重处理基本指南。
按顺序执行任务可能不是一个好主意。如果第二个任务的输入不是第一个任务的输出,那么你就在浪费时间和 CPU。
你可能知道,Python 的 全局解释器锁 (GIL)机制一次只允许一个线程执行 Python 字节码。这是一个严重的限制,可以通过更改 Python 解释器或实现基于进程的并行技术来避免。
今天,您将学习如何使用 Python 和concurrent.futures
库并行执行任务。您将通过一个实际例子理解这个概念——从多个 API 端点获取数据。
这篇文章的结构如下:
- 问题描述
- 测试:按顺序运行任务
- 测试:并行运行任务
- 结论
你可以在这里下载这篇文章的源代码。
问题描述
目标是连接到jsonplaceholder.typicode.com——一个免费的假 REST API。
您将连接到几个端点并获取 JSON 格式的数据。总共会有六个端点。不是很多,Python 很可能在一秒钟左右完成任务。对于演示多处理能力来说不太好,所以我们将增加一些趣味。
除了获取 API 数据,程序还会在发出请求之间休眠一秒钟。由于有六个端点,程序应该在六秒钟内什么都不做——但是只有当调用按顺序执行时。
我们先测试一下没有并行的执行时间。
测试:按顺序运行任务
让我们看一下整个脚本,并对其进行分解:
在URLS
变量中存储了一个 API 端点列表。你将从那里获取数据。在它下面,你会发现fetch_single()
功能。它向一个特定的 URL 发出 GET 请求,然后休眠一秒钟。当获取开始和完成时,它也打印。
该脚本记下开始和结束时间,并将它们相减以获得总执行时间。在来自URLS
变量的每个 URL 上调用fetch_single()
函数。
运行该脚本后,您将在控制台中获得以下输出:
图 1-没有多重处理脚本输出(作者提供的图片)
简而言之,这就是顺序执行。这个脚本在我的机器上花了大约 7 秒钟完成。你可能会得到几乎相同的结果。
接下来让我们看看如何使用并行性来减少执行时间。
测试:并行运行任务
让我们看一下脚本,看看有什么变化:
concurrent.futures
库用于实现基于进程的并行性。URLS
和fetch_single()
都是一样的,所以没有必要再重复一遍。
下面才是有趣的地方。你必须使用ProcessPoolExecutor
类。根据文档,它是一个使用进程池异步执行调用的类[1]。
这里的with
语句是为了确保在任务完成后所有的东西都被清理干净。
可以使用submit()
函数来传递想要并行执行的任务。第一个参数是函数名(确保不要调用它),第二个参数是 URL 参数。
运行该脚本后,您将在控制台中获得以下输出:
图 2 —多重处理脚本输出(图片由作者提供)
执行时间仅用了 1.68 秒,比之前有了显著的提高。这是任务并行运行的具体证明,因为顺序执行无法在 6 秒内完成(睡眠调用)。
结论
这就是关于 Python 基于流程的并行性的最基本指南。还有其他基于并发性的方法来加速您的脚本,这些将在下面的文章中介绍。
如果你想看更多高级并行教程,请告诉我。这些将涵盖数据科学和机器学习中的真实用例。
感谢阅读。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
了解更多信息
- 每个数据科学家必读的 3 本编程书籍
- 如何让 Python 静态类型化——基本指南
- 用 Python 进行面向对象编程——你需要知道的一切
- Python 字典:你需要知道的一切
- 介绍 f 字符串 Python 中字符串格式化的最佳选项
参考
[1]https://docs.python.org/3/library/concurrent.futures.html
原载于 2021 年 1 月 16 日 https://betterdatascience.comhttps://betterdatascience.com/python-concurrency/。
Python 解析库:反转 F 字符串的简单方法
我们有时需要反过来。
马修·斯特恩在 Unsplash 上的照片
字符串插值是使用占位符修改字符串的过程。结果字符串包括占位符的值。在 Python 中,format 方法和 f 字符串是两种常用的字符串插值方法。
在跳到解析库之前,做几个例子来演示什么是字符串插值是有帮助的。
folder = "notebook"
subfolder = "parse"file_path = f"documents/{folder}/{subfolder}"print(file_path)
documents/notebook/parse
占位符用花括号表示,它们的值包含在输出中。
这是另一个例子。
name = "John"
age = "23"print(f"{name} is {age} years old.")
John is 23 years old.
我们现在更熟悉 f 弦和一般意义上的弦插值。解析库所做的是字符串插值过程的逆过程。
可以使用解析库提取字符串中的值。我们将做几个例子来解释清楚这个过程。
解析库可以很容易地与 pip 一起安装。如果你用的是 jupyter 笔记本,就加“!”在匹普之前。
!pip install parse
从语法上分析
在第一个示例中,我们使用预定义的文件夹和子文件夹名称创建了一个文件路径。让我们使用解析库从路径中获取文件夹名。我们首先需要从解析库中导入解析函数。
from parse import parsefile_path = "documents/notebook/pandas"parse("documents/{folder}/{subfolder}", file_path)
<Result () {'folder': 'notebook', 'subfolder': 'pandas'}>
我们传递文件路径以及表示占位符的字符串。parse 函数返回一个结果对象,但是我们可以通过添加命名方法使它看起来更漂亮。
parse("documents/{folder}/{subfolder}", file_path).named
{'folder': 'notebook', 'subfolder': 'pandas'}
假设我们有一个格式相同的路径列表,我们需要提取文件夹和子文件夹的名称。这个任务可以通过解析器功能和列表理解的完美结合来轻松完成。
file_paths = [
"documents/notebook/pandas",
"documents/notebook/parse",
"documents/notes/python"
][parse("documents/{folder}/{subfolder}", path).named for path in file_paths][{'folder': 'notebook', 'subfolder': 'pandas'},
{'folder': 'notebook', 'subfolder': 'parse'},
{'folder': 'notes', 'subfolder': 'python'}]
搜索
解析库还提供了一些其他函数,在特殊情况下会很方便。例如, search 函数在字符串中查找某种格式。因此,我们不必提供整个字符串的确切格式。
from parse import searchtxt = "Name: Jane, Department: Engineering, Age: 22"search("Name: {Name},", txt).named
{'Name': 'Jane'}
当我们不确定确切的字符串时,也可以使用搜索功能。因此,它提供了额外的灵活性。
考虑以下字符串:
txt = "The department of civil engineering has 23 employees"
txt2 = "The civil engineering department has 23 employees"
我们需要找到文本中雇员的数量。我们可以用同样的子模式找到它。
search("has {number_of_employees} employees", txt).named
{'number_of_employees': '23'}search("has {number_of_employees} employees", txt2).named
{'number_of_employees': '23'}
芬达尔
解析库中另一个有用的函数是 findall 函数。如果有多个我们感兴趣的相同模式的部分,我们可以使用 findall 而不是 parse。
以下示例显示了 findall 函数的一种可能用法。
from parse import findallpaths = "documents/notebook/pandas/ documents/notebook/parse/ documents/notes/python/"findall("documents/{folder}/{subfolder}/", paths)
<parse.ResultIterator at 0x2056556ab80>
我们有一个很长的字符串,其中包含多个格式相同的文件路径。findall 函数,顾名思义,查找给定路径结构的所有文件夹和子文件夹名称。
默认情况下,它返回一个迭代器,但我们可以很容易地将其转换成一个列表。
list(findall("documents/{folder}/{subfolder}/", paths))[<Result () {'folder': 'notebook', 'subfolder': 'pandas'}>,
<Result () {'folder': 'notebook', 'subfolder': 'parse'}>,
<Result () {'folder': 'notes', 'subfolder': 'python'}>]
该列表包含 3 个结果对象。我们可以将命名方法分别应用于这些对象。
a = list(findall("documents/{folder}/{subfolder}/", paths))a[0].named
{'folder': 'notebook', 'subfolder': 'pandas'}
结论
Parse 是一个非常实用的函数库。正如我们在示例中看到的,它提供了在字符串中查找模式和值的简单方法。某种意义上是字符串插值的逆向运算。
还有其他方法来执行相同的任务。例如,本文中的例子也可以用 regex 来完成。但是,对于某些操作来说,正则表达式可能太复杂了。我觉得解析库提供了更简单的解决方案。
如果你想了解更多关于解析库的知识,请随意访问官方文档。
感谢您的阅读。如果您有任何反馈,请告诉我。
带寄存器的 Python 多态性| Python 模式
学习一种模式来隔离包,同时扩展 Python 代码的功能。
动态多态是面向对象编程最强大的特性,它允许我们针对那些实际行为只在运行时定义的抽象进行编程。根据 Robert C. Martin 的说法,这也是真正定义 OOP 的唯一特性。
多态性由两部分组成:具有类型相关实现的方法和类型不可知的方法调用。在 python 代码中:
class TypeA:
def speak(self):
print("Hello, this is an object of type TypeA")class TypeB:
def speak(self):
print("Greetings from type TypeB")def agnostic_speak(speaker):
speaker.speak()agnostic_speak(TypeA())
agnostic_speak(TypeB())>> Hello, this is an object of type TypeA
Greetings from type TypeB
这个简单的例子展示了多态方法 speak()和调用它的通用函数,而不知道调用该方法的对象的实际类型。
对于这样一个简单的例子,一个基于参数类型的条件语句(if/else)可以完成这个任务,但是它也有缺点。首先,条件代码使得函数更难阅读。在这个版本中,我们立即看到它做了什么,它让一个对象说话,而对于条件代码,我们需要首先理解不同的分支做什么。此外,如果将来我们想要添加更多的说话方式,我们将需要返回到这个函数并修改它以添加新的分支。反过来,新的分支将使代码越来越不可读。
假设我们有一个文本分类应用程序,它在输入中接收一行文本,并输出一个代表其主题的标签。像任何好的机器学习项目一样,我们在不同的模型上执行许多迭代,我们希望我们的代码使用它们。当然,我们不希望一个大的 switch 语句(if-elif-elif-…-else)带有运行任何单一模型的逻辑!
出于示例的原因,所有模型共享相同的输入/输出词汇表,即相同的映射 word->input index 和 output index->label。唯一的区别在于底层模型所执行的操作。
在我们代码的第一个版本中,我们只有两个模型架构:一个前馈网络和一个 LSTM 网络,它们提供多态的 forward() 方法来封装每个模型的逻辑:
# main.py
from models import FfModel, LstmModeldef main(args):
model_type = args.model_type
model_path = args.model_path
if model_type == "ff":
model = FfModel(model_path)
elif model_type == "lstm":
model = LstmModel(model_path)
else:
raise NotImplementedError("Unrecognizer type %s" % model_type) outputs = [] with open(args.input_text, 'r') as fid:
for line in fid:
word_ids = convert_to_id(tokenize(line))
output = model.forward(word_ids)
outputs.append(convert_to_labels(output)) show_results(outputs)
该代码包含许多未定义的函数,以便让我们专注于本文的相关部分。在这个片段中,我们可以看到模型类型是由用户提供的参数。它用于选择哪种模型类型,由一个类表示,应该用来加载和使用我们保存的模型。注意,这两个类都被导入到主文件中。在代码片段的后半部分,我们有对 forward 的多态调用,它根据深度学习模型的类型正确地执行深度学习模型操作。最后,我们有在文本和不依赖于模型的神经网络格式之间转换输入和输出的函数。
这段代码片段成功地提供了多态行为,但它显然打破了软件设计的坚实原则之一的开/闭原则。这个原则规定我们的程序应该对扩展开放,但对修改关闭。
在实践中,这意味着每当我们想在程序中添加一个新的模型类型时,我们必须为该类型创建一个新的类,然后将其导入 main.py,最后在 if/else 语句中为其添加一个新的分支。我们的程序对扩展开放,但也对修改开放。如果我们想将允许的类型添加到我们的软件帮助中,这样用户就可以很容易地发现它们。每当我们添加一个新的类型时,我们也应该把它的名字添加到允许的类型列表中,否则会给用户带来很大的困扰。
改进上述代码的第一种方法是用一个函数替换条件分支,该函数将模型类型及其路径作为输入,并返回构建的对象(类似于工厂方法)。上面的代码将变成:
# main.py
from models import model_factorydef main(args):
model_type = args.model_type
model_path = args.model_path model = model_factory(model_type, model_path)
outputs = [] with open(args.input_text, 'r') as fid:
for line in fid:
word_ids = convert_to_id(tokenize(line))
output = model.forward(word_ids)
outputs.append(convert_to_labels(output)) show_results(outputs)
现在模型选择被移动到 init。模型包的 py 文件,在这里我们还用对字典的调用替换了条件代码:
# models.__init__.py
__all__ = ["factory_method"]from .ff_model import FfModel
from .lstm_model import LstmModel__MODEL_DICT__ = {
"ff": FfModel,
"lstm": LstmModel
}def factory_method(model_type, model_path):
return __MODEL_DICT__[model_type](model_path)
这里我们只导出 factory_method,它从字典中选择正确的类型名(在 python 中是用于创建该类型对象的工厂),构建一个对象并将其返回给调用者。字典是将条件代码简化为线性流的简单有效的方法。
有了这段代码,我们的主文件就可以忽略现有的模型类型,我们可以添加或删除类,而根本不用修改主文件。然而,我们只是将开放修改代码从 main.py 移到了这个新的 __init.py 文件中。我们通过将主文件与可能的模型修改隔离开来,实现了改进,但是 init 中的代码。出于同样的原因,py 仍然可以修改。
我们能做得比这更好吗?当我们添加一个新的模型类型时,我们可以用这样的方式编写代码吗?我们只需添加新的类和一个名称来标识它,这样就足以将它添加到 MODEL_DICT 字典中了。
事实证明,这不仅在 python 中是可能的,而且只需要一堆代码就可以实现。该机制由两部分组成:
- **类发现:**一种无需为类编写显式导入语句即可导入类的算法。
- **注册:**被发现的类自动调用的函数,被添加到字典中,如 MODEL_DICT
让我们轮流看他们。
自动发现
我们如何导入类而不为它们或者甚至包含它们的文件(模块)写一个显式的导入呢?或者更根本地说,在给定这些约束的情况下,我们如何让 Python 解释器在运行时知道它们?
这个解决方案是通过 importlib 包中的 import_module 函数实现的。来自 importlib 官方文档:
[…]实现
[import](https://docs.python.org/3/reference/simple_stmts.html#import)
的组件在这个包中公开,使得用户更容易创建他们自己的定制对象(一般称为导入器)来参与导入过程。
正是我们需要的!让我们看看可以添加到 init 中的代码。py 使它成为我们模型类的自定义导入器:
# Iterate all files in the same directory
for file in os.listdir(os.path.dirname(__file__)):
# Exclude __init__.py and other non-python files
if file.endswith('.py') and not file.startswith('_'):
# Remove the .py extension
module_name = file[:-len('.py')]
# Assume src to be the name of the source root directory
importlib.import_module('models.' + module_name)
通过这四行代码,我们的软件自动导入模型包中的所有模块。当一个模块被导入时,它的代码被执行,所以我们的下一步是实现一个机制,让我们的类把它们自己添加到我们的 MODEL_DICT。
子类注册
第一种方法修改了初始化子类的 Python 机制。幸运的是,Python 公开了在第一次执行类定义时调用的方法 init_sub_class。每个类都自动调用父类的 init_sub_class 并将其类型作为参数。因此,我们可以在父类中集中注册新类的逻辑:
# models.model.py__MODEL_DICT__ = dict()class Model:
def __init_sub_class__(cls, **kwargs):
assert "name" in kwargs
super().__init_sub_class__()
if kwargs["name"] in __MODEL_DICT__:
raise ValueError("Name %s already registered!" % name)
__MODEL_DICT__[kwargs["name"]] = cls
init_sub_class 接受一个名为 cls 的参数作为输入,这是类类型参数的 Python 约定,以及存储在 kwargs 附加库中的任意数量的键值对。我们首先检查 “name” 是否作为一个键存在于附加参数中,因为如果我们不知道用哪个名字注册一个类,我们就不能在字典中注册它。然后,如果以前没有注册过具有该名称的类,只需将它添加到字典中,否则程序会退出并显示一个错误,通知用户存在重复。
现在,我们只需要每个子类将其名称作为附加参数传递给 init_sub_class。这不是通过直接调用函数来完成的,而是在定义父类时完成的:
# models.ff_model.py
from .model import Modelclass FfModel(Model, name="ff"):
def __init__(self, *args, **kwargs):
# Construct object def forward(self, x):
# Operations for computing output probabilities of x
类实现不知道注册过程,它只通过父类名旁边的赋值 name="ff "出现。该赋值以及您可能愿意添加的任何其他赋值将构成模型 init_sub_class 的**kwargs。
同样,对于 LstmModel:
# models.lstm_model.py
from .model import Modelclass LstmModel(Model, name="lstm"):
def __init__(self, *args, **kwargs):
# Construct objectdef forward(self, x):
# Operations for computing output probabilities of x
在 init 时。py 我们需要执行发现代码并公开一个工厂方法来访问字典
# __init__.py
__all__ == ["factory_method"] import os
import importlibfrom models.model import Model, __MODEL_DICT__ def factory_method(name, path):
return __MODEL_DICT__[name](path)
for file in os.listdir(os.path.dirname(__file__)):
if file.endswith('.py') and not file.startswith('_'):
module_name = file[:file.find('.py')]
module = importlib.import_module('models.' + module_name)
寄存器功能
第二种方法是定义一个包装我们的类的“注册”函数。register 函数的内部工作有点复杂,所以请原谅我。当包含由我们的注册函数包装的类的模块第一次被执行时,它用它的参数运行包装函数。包装函数定义了一个内部函数,该函数将一个类类型作为输入,并对其进行处理。此外,内部函数可以访问包装函数中的变量,因为它是一个闭包。register 函数简单地返回它的内部函数,这个内部函数又以包装的类类型作为它的输入被立即执行。内部函数是实际注册类的函数,然后返回将由 python 解释器添加到当前名称空间的类类型。
这个过程在 init 之间再次拆分。py 文件和包含实际类的模块文件。
# models.__init__.py
__all__ = ["factory_method"]
import os
import importlib
from .model import Model
def factory_method(name):
return __MODEL_DICT__[name]
__MODEL_DICT__ = dict()
def register_function(name):
def register_function_fn(cls):
if name in __MODEL_DICT__:
raise ValueError("Name %s already registered!" % name)
if not issubclass(cls, Model):
raise ValueError("Class %s is not a subclass of %s" % (cls, Model))
__MODEL_DICT__[name] = cls
return cls
return register_function_fn
for file in os.listdir(os.path.dirname(__file__)):
if file.endswith('.py') and not file.startswith('_'):
module_name = file[:file.find('.py')]
module = importlib.import_module('models.' + module_name)
同样, factory_method 是我们的 main 调用的函数,它基本上是我们字典的包装器。请注意,在我们的内部 register_function_fn 中,我们可以执行我们通常可以在任何函数中执行的任何操作,因此我们额外检查了给定的类是 Model 的子类。这是保持基于层次结构的多态性所必需的,因为我们不能保证 register 类确实是一个模型。
另一种可能的检查可以基于鸭类型,并且将检查类中是否存在转发方法,而不是检查其类型。python 的动态性也允许我们不执行任何检查,但是当我们使用 buggy 模型时,这种勇敢会导致令人讨厌的错误出现。相比之下,在我们的注册机制中插入检查将在每次运行程序时检查所有模型的一致性。在静态类型语言中,这种检查是由类型检查器执行的,而 Python 为我们提供了更多的动态性,但也为我们清理自己的混乱提供了更多的责任。
现在,我们只需要使用 register_function 作为我们的类的装饰器,以使它的功能如上所述:
# models.ff_model.py
from models.model import Model @register_function("ff")
class FfModel(Model):
def __init__(self, *args, **kwargs):
# Construct object def forward(self, x):
# Operations for computing output probabilities of x
通过使用 register_function 作为装饰器,注册机制在类的外部,因此它可以完成它的工作,然后模型类不需要任何关于注册的代码。
该示例的工作方式如下:
- register_function 以*“ff”*作为唯一参数被调用
- 它返回 register_function_fn 封闭绑定 name=“ff”
- 用 cls=FfModel 调用寄存器 _ 函数 _fn
- register_function_fn 在我们的 MODEL_DICT 中注册键值对*“ff”= FfModel*,并将 ff MODEL 返回给解释器
- 现在可以通过函数 factory_method() 在应用程序中使用 FfModel,并且不需要显式导出。
第一次很难掌握像这样的注册功能的工作原理,主要是因为所有的操作都是在启动阶段执行的。然而,如果你能遵循上面的解释,你将最终掌握一个强大的工具,它的应用远远超出了这个例子。
与前面的解决方案相比,register 函数的难度为我们带来了更多的灵活性,因为它没有将我们限制在类的层次结构中,也可以用于函数而不是类。此外,从单一责任原则 (SRP)的角度来看,这段代码更好,因为注册子类的责任被交给了一个只负责注册子类的函数,而不是父模型类。
结论
类发现是一个强大的工具,它通过 Python 项目的两个组件之间的抽象接口提供多态行为。
最终的设计在不同的包之间有更少的显式依赖,而在同一个包内有更多的依赖。然后,它强制不同包及其内部内聚性的解耦。
此外,我们还看到了这种模式是如何产生更加尊重可靠原则的代码的。
当一个包主要由相同层次结构中的类组成时,我推荐使用它,这样就可以用不同的实现达到相同的目的。只有在定义代码的地方才需要修改代码,这让我们在开发的时候心情愉快。
承认
我从脸书的 FAIRseq 开源项目中学会了如何使用注册函数。你可以在这里找到他们当前的实现或者从我的老叉找到一个更简单的版本。
init_sub_class 方法的一个很好的解释可以在堆栈溢出中找到。
Python 支持的蒙特卡罗模拟
用 SciPy 的概率分布进行情景分析
本教程将演示如何在 Python 中建立蒙特卡罗模拟模型。我们将:
- 使用 SciPy 的内置分布,具体来说:正态、贝塔、威布尔;
- 为 beta-PERT 分布添加新的分布子类;
- 用拉丁超立方体抽样抽取随机数;
- 并建立三个蒙特卡罗仿真模型。
0.属国
除了我们的全天候包 pandas 和 numpy 之外,该脚本还导入了 SciPy 的 stats 库。
蒙特卡罗模拟的概念
蒙特卡罗(MC)方法是一种模拟技术,为模型的输出变量构建概率分布,其中一些输入参数是随机变量。MC 方法有时被称为多概率模拟技术,因为它整合了多个随机变量,而这些变量的综合效应很难用封闭形式的方程来描述。
20 世纪 40 年代末,约翰·冯·诺依曼和斯坦尼斯劳·乌姆在洛斯阿拉莫斯实验室工作时发明了 MC 方法。当他们试图应用确定性方法计算中子碰撞时,他们走进了死胡同。乌姆的想法是使用随机抽样,由早期的“超级计算机”ENIAC 支持,以获得近似解。政府保密条例规定了一个代号;冯·诺伊曼和乌姆的一位同事建议把这个地方命名为蒙特卡洛,乌姆的叔叔在二战前经常去那里的赌场:他“只是必须去蒙特卡洛”赌博。( U 日上午)
MC 模拟问题的一个例子:一个企业想要模拟一个新产品的未来成功,并建立如下的模拟模型:
- 预期销售量,v,作为一个随机变量,遵循 beta-PERT 分布,从 3 点估计值得出;
- 产品的价格 p 将通过谈判确定——一些客户可能会要求批量折扣或提前付款折扣;计划者决定将公司将实现的平均价格的不确定性建模为平均值为 20、标准差为 2 的正态随机变量;
- 产品的原材料成本 m 将受到即将到来的供应商价格上涨的影响,由另一个正态分布来估计;
- 该模型应该提供多种结果:收入,利润,总成本。
一般来说,模拟模型采用被定义为随机变量的输入参数;然后,它返回每个输出变量的概率分布,每个变量都是其输入参数的函数。该函数可以是简单的和或积、输入的指数运算,或者——我们将在示例中看到——包含嵌套随机变量的高阶函数。目标变量将显示一个密度函数,描述输入变量的分布如何相互作用。
仿真模型通过从输入分布中采样来导出输出分布。1,000 或 100,000 次迭代提供了描述每个输入变量行为的数据点。输出变量的等式将这些输入转换为输出变量的尽可能多的样本。一旦我们获得了这个样本,我们就可以像任何内置的 SciPy 分布一样检查它的模式——它的平均值、分位数和离群值倾向。
2.拉丁超立方体采样
NumPy 的 random 库提供了用统一随机数填充数组的方法。SciPy 的每个分发对象都带有生成随机变量的 rvs() 方法。然而,蒙特卡罗模拟需要大量的随机数,对于这种情况,分层抽样方法,如拉丁超立方体抽样(LHS) 是更好的方法。
LHS 将样本空间分成不重叠的单元,每个单元具有相等的概率。LHS 确保从样本空间的完整范围内更均匀地抽取随机数。它防止采样随机数的聚集,这会扭曲频率曲线。LHS 样本将导致更精确的模拟结果。这将导致更低的标准误差,更少的迭代。
出于演示的目的,我们使用 SciPy 的 LatinHyperCube 采样器创建了一个由 10 个随机数组成的小数组。
制服(0;1)随机数在生成之后,可以被重新缩放以填充更大的范围,例如在 0 和 100 之间。然而,对于我们的目的,0 到 1 之间的标准随机数满足要求。
在下一章中,我们将使用 LHS 样本来生成特定分布的随机变量:PERT、正态和威布尔函数。
3.模拟中模拟不确定性的概率分布
3.1 用于专家评估建模的 PERT 分布
我的上一篇文章“用 beta-PERT 分布对专家评估建模”是一篇教程,解释了 PERT 分布 ( Python 场景分析:用 beta-PERT 分布对专家评估建模|走向数据科学)。
如果一个领域、过程或行业专家可以为一个随机过程提供所谓的三点估计(三点估计),包括最坏情况、可能情况和最好情况的结果,那么 pert 函数就可以将这些点连接起来,这是非常确切的。它会将这些点转化为概率分布,我们可以将它分配给模拟模型中的随机变量。
SciPy 的 123 个分布目录不包含 PERT 函数。因此,我们将其创建为继承自 SciPy 的 rv_continuous 父类的新子类。
对于我们的例子,我们用四个参数来实例化 PERT 子类,这四个参数预测新产品的销售量。
我们选择这个 PERT 分布来模拟预期的销售数量,最有可能的值是 12,000 个单位,最小为 8,000 个单位,最大为 18,000 个单位。
其平均预期值将为 12,333 个销售单位。它有一个小的正偏度,仍然类似于正态分布;和-0.62 的中度负过度峰度,这使其成为一个宽峰或大体积分布,其尾部的异常值比正态分布少。
第 3 行抽取 N=10,000 个均匀随机变量的 LHS 样本。然后第 4 行将该数组作为其输入参数。百分点 函数 ppf() 将 10,000 个标准统一数字解释为概率,并计算 10,000 个 PERT 分位数,用这些分位数填充数组 randP。
直方图显示了模拟的 PERT 分布的形状。
3.2 正态分布
我们应用相同的方法(减去创建新的分布子类的需要)从正态分布中抽取 10,000 个 LHS 样本,我们期望这些样本反映销售价格和原材料单位成本中固有的不确定性。平均售价将为€ 20,标准差为€ 2。
第二个正态分布模拟原材料单位成本,平均值为€ 13,标准偏差为€ 1.40。
3.3 模拟 1-模拟随机变量的总和与乘积
我们创建了三个随机变量,代表三个因素的不确定性,这三个因素将决定新产品产生利润的机会或损失的风险:
- 体积 v 的 1x PERT
- 2x 正常——对于销售价格 p 和原材料单位成本 m;
我们假设其他成本,那些与供应商价格无关的成本,可以通过一个确定性变量 o 来反映。
为了计算模拟模型的目标变量——产品的毛利 GP,我们将随机变量联系在一起,如下所示:
- v =数量,p =价格,m =原材料单位成本,o =其他单位成本
- GP = v * (p — m — o)
作为第二个产出变量,我们可以模拟与 GP 平行的收入 R。一般来说,一旦我们定义了输入随机变量,我们就可以用任意多的输出变量建立模拟模型。每个输出变量都可以由一个或多个输入随机变量组合而成,并由它自己的 10,000 个变量的数组来描述。
- R = v * p
这就建立了我们简单的模拟模型。
为了查看返回毛利随机变量 GP 的分布的数组的属性,我们编写了一个函数 dist_properties() ,该函数将读取数组并返回其矩和选定的分位数。
对于偏度和峰度,我们使用 SciPy 的 skew() 和峰度 () 函数,其结果需要借助 numpy 的 asscalar() 转换方法从数组转换成平面数。我们在字典 dict_moments 中收集它们的值,并在其中添加度量的名称。第 14 行中的 list comprehension 逐行打印字典的内容。
然后我们计算第 17ff 行中的分位数,将它们收集到另一个字典中, dict_quantiles ,并使用第 27 行中的 list comprehension 将其打印出来。
我们合并两个字典,矩和分位数,形成一个综合字典, dict_metrics 。
平均利润将达到€ 49,250,接近中位数。
偏度和峰度都相当适中,这意味着与正态分布相比,有适度的离群值倾向。
如果销售价格和原材料单位成本同时趋向于最坏的结果,列表中的分位数揭示了新产品将产生损失的 5%的风险。利润将以 90%的概率超过€ 10,731(10%的分位数)。
在下一章中,我们将更进一步,建立一个高阶仿真模型。虽然第一个模型仅从随机变量的和与积中组装输出变量,但我们现在将开发一个包括嵌套随机变量的模拟模型。
3.5 模拟 2 —嵌套分布
如果你读过我以前的文章(概率分布介绍和用 Python 的 SciPy 拟合分布),你会记得我们已经应用了威布尔分布来解决故障前时间或部件寿命问题。一个部件的寿命通常可以由一个威布尔分布来模拟,其形状反映了故障率和一个设定所谓的特征寿命的比例参数。
第一艘前往火星的宇宙飞船将配备电子电路板,其典型寿命可能为 5 万个工作小时。为了估计船舶设计中需要的冗余度,我们将模拟多少小时后所有电路的哪一部分会烧坏。
让我们假设,非常符合现实,威布尔分布的两个参数——形状和特征寿命——是不确定的。因此,我们将这些分布参数估计为它们自己的随机变量。威布尔形状及其标度可以在由它们的分布模式设定的边界内波动。
- 对于形状参数,工程团队估计它应该正态分布在平均值 1.5 左右,标准偏差为 0.1。
- 对于特征寿命,我们采用工程团队在 45,000、50,000 和 60,000 小时运行时提供的最大、可能和最小值的三点估算值。然后我们推导出一个 PERT 分布来反映不确定性的范围。
两个辅助函数通过拉丁超立方体采样抽取 10,000 个随机变量。
- wei_shp() 返回正态分布的形状参数。
- wei_charlife() 返回特征寿命的 PERT 变量数组。
- 我们将 Weibull 位置参数(也称为等待时间)设置为 0,这意味着产品故障会在生产完成和质量测试开始后立即出现。
在第 7 行中,我们将随机变量组合成一个威布尔输出变量,表示故障前的时间。威布尔函数调用辅助函数来获取其形状和比例参数。目标变量 rand_CL 将保存一个由 10,000 个模拟输出组成的数组,这些输出以小时为单位表示组件的寿命。
有三种嵌套分布的结果:PERT 和正态随机变量充当威布尔分布的参数。我们可以将结果标记为灵活的或随机的威布尔分布,而不是参数为点值的固定分布。
对于如何查看结果的属性,我们可以在两个选项之间进行选择:
- 将它作为一个数组进行分析,就像我们对之前的模拟结果所做的那样
- 应用 SciPy 的 rv_histogram 类,它将输出数组打包成一个直方图,并将其转换成一个“真实的”SciPy 概率分布,为此我们可以调用像 pdf 和 ppf 这样的分布函数。
让我们来看看直方图类。
图表用蓝色显示了我们在数组 rand_CL 中模拟的装箱寿命。在 orange 中,它绘制了概率密度函数,这个函数是由 rv_histogram 类从数组中得到的。
函数 histdist_properties() 计算 histdist 概率分布的属性。这个函数在某些方面与我们之前写的函数*distribution _ properties()*不同。 rv_histogram 类并不是在所有情况下都提供相同的分布属性。例如,最小值和最大值必须从。*支持()*方法。
我们调用 histdist_properties() 函数并获得列表度量。数据帧不仅包含累积分布函数和分位数,还包含一列随机变量。采样功能*。rvs()* 使我们能够从我们的输出分布中抽取一些随机数——或者如果需要的话抽取几千个,尽管没有 LHS 方法的分层抽样。
上面,我们分析了 rv_histogram 从数组中导出的连续分布。如图所示,分布曲线与阵列数据并不完全相同。
下面,我们对 10,000 个输出数字的原始数组调用函数 dist_properties() ,并解释矩和分位数。
- 部件的平均寿命将达到 45,647 小时;
- 寿命将是右偏的,自然地,异常值将持续超过 100,000 小时;
- 峰度是独特的——分布是薄峰的,其尾部有长期存活成分的倾向;
- 11,076 小时后,10%的部件将会烧坏。
任务控制中心告诉我们 11,000 小时的操作将标志着关键的阈值。在完成这些使用时间后,电路将不再是关键任务。分位数意味着,就电路板而言,飞船在设计时应该考虑到至少 10%的冗余度:增加备用电路板,这些电路板将自动打开,作为那些开始出现故障的电路板的替代品。
所以我们的太空船几乎准备好发射了。
3.6 模型 3:固定参数模拟
作为最后一项练习,让我们检查威布尔模型对其形状和特征寿命的不确定性有多敏感。
我们将使用“固定形式”的威布尔分布进行蒙特卡罗模拟。
- 形状参数将固定为平均值 1.5;
- 特征寿命:PERT 分布的平均值:50,500 小时。
我们再次调用 dist_properties() 函数,并将其指标插入到数据帧中,这样我们就可以并排比较固定威布尔和之前嵌套的“灵活”威布尔。
正如预期的那样,我们看到固定威布尔变量的标准偏差较低,这是在形状和比例参数没有不确定性的情况下产生的。但是差别很小。显然,柔性威布尔模型的形状和尺度参数的不确定性非常接近于我们在固定威布尔模型中使用的平均值。较低的偏度和峰度显示出异常值和不对称性的倾向有所降低。
参数具有不同值或不同分布的模拟可能会显示由增加的不确定性引起的较大差异。
4.结论
今天的教程到此结束。
我们浏览了三个蒙特卡罗模拟的例子,它们是用 SciPy 库提供的工具箱创建的。
- 第一个模型——利润模拟——展示了随机变量的简单求和与乘积。
- 第二个模拟——威布尔失效时间——解释了嵌套随机变量的概念,即概率分布的参数本身就是随机变量。
- 第三次模拟大胆假设分布的参数是精确已知的。如果我们在模拟模型中忽略不确定性的来源,我们可能会低估可能结果的传播,除非不确定性浓缩为参数的平均值。
Jupyter 笔记本可以从 GitHub 下载:h3ik0th/montecallosim:用 SciPy(github.com)进行蒙特卡洛模拟
- 蒙特卡洛,图片由卢卡·阮来自皮克斯拜,免费用于商业用途
- 蒙特卡洛赌场,图片由来自 Pixabay 的 Hans Braxmeier 提供,免费用于商业用途
- 由彼得·H从皮克斯拜工厂,免费用于商业用途
- 所有其他图片:作者
Python 轻松打印彩色文本
如何在 Python 中使用 Console.log、console.warn 和 console.error
让我们用一些颜色来增加那些无聊的控制台输出的趣味吧!(图片由作者提供)
上图两张截图哪个更好看?我肯定会选择正确的!这些颜色会立即将你的注意力吸引到重要的事情上,并给你额外的信息,让你一目了然。
本文涉及两个主题:首先,我们将了解如何在终端上打印颜色
- 终端中打印颜色的工作原理
- py-控制台;一个 Python 包,允许您轻松打印彩色输出
在这篇文章的结尾,你将能够打印彩色输出,那么我们还在等什么呢?我们来编码吧!
1.了解打印
首先,我们将检查终端需要什么来打印彩色文本。这将向您展示如何在最底层打印彩色文本。在接下来的部分中,我们将用更容易使用的方法来代替这种方法。
在 Python 中执行print
功能时,文本出现在 终端 中。我们的目标是给这个值添加一些数据,以便我们的终端知道在显示文本时添加一些颜色。这些额外数据是ANSI escape character sequences
,用于控制光标位置、颜色和字体样式,例如在终端中。终端将这些序列解释为命令,而不是仅仅显示的文本。
在下面的例子中,我们使用 Python 告诉终端打印文本“这是我们的消息”,文本为白色,背景为黄色:
print('\x1b[0;39;43m' + 'This is our message' + '\x1b[0m')
我们控制台中的输出(图片由作者提供)
ANSI 代码的第一段(\x1b[0;39;43m
)是终端根据指定参数给所有后续文本着色的命令。我们可以在0;39;43
部分用整数指定样式、文本颜色和背景颜色。结尾的第二段 ANSI 代码(\x1b[0m
)告诉终端重新设置颜色选项,这样以后的文本就不再是白色和黄色了。
这种方法的问题是,这种方法在每个平台上都不统一;ANSI 代码可以在 Unix 和 MAC 上运行,但在 Windows 上不受支持。我们也不想打出这些具体的、难以理解的代码。让我们找到一种更简单的跨平台方法!
2.py-控制台;用 Python 轻松打印颜色
Py-console是我创建的一个软件包,它使打印颜色变得超级简单。它模仿 JavaScripts 众所周知的方法,如console.log
、console.warn
、console.error
等。它还增加了一些更多的功能。
装置
首先我们将安装 py-console:
pip install py-console
记录、警告、错误、成功、信息
安装后,我们可以简单地调用 5 个函数之一,以指定的颜色打印文本:
from py_console import consoleconsole.log("log")
console.warn("warn")
console.error("error")
console.success("success")
console.info("info")
这会产生:
py-console 简单功能的输出(图片由作者提供)
切换时间和设置时间戳
我们还可以通过为控制台或 per 方法指定时间戳来为输出添加时间戳:
console.setShowTimeDefault(True)
or
console.log('log', showTime=True)
这将在您要打印的字符串前添加时间:
带时间戳的日志记录(图片由作者提供)
此外,您还可以设置时间格式。点击查看关于此的更多信息。下面的示例将时间格式设置为包括年、月和日。****
console.setTimeFormat('%y-%m-%d %H:%M:%s')
更严重的警告
在方法中,我们可以指定一个“severe”标志,以便我们有更清晰的输出,反转文本颜色和背景颜色:
from py_console import consoleconsole.log("log", severe=True)
console.warn("warn", severe=True)
console.error("error, severe=True")
console.success("success, severe=True")
console.info("info, severe=True")
这将产生以下输出:
这些消息有点严重(图片由作者提供)
突出
最后,我们还可以突出显示字符串的某些部分。看看这个我们使用 console.warn 函数的例子:
在同一个控制台中使用不同的文本颜色和背景颜色
结论
在本文中,我们主要讨论了终端如何打印彩色文本来更清晰地显示重要信息。然后我们看了一下的 py-console** 以及它如何让彩色输出变得非常容易。**
我希望我已经阐明了如何指示终端打印颜色,并且我希望我已经为您留下了一个创建漂亮的控制台输出的不错的包。如果你有建议/澄清,请评论,以便我可以改进这篇文章。
同时,请查看我的其他关于各种编程相关主题的文章:
- 用 FastAPI 用 5 行代码创建一个快速自动记录、可维护且易于使用的 Python API
- 从 Python 到 SQL——安全、轻松、快速地升级
- 创建并发布你自己的 Python 包
- 创建您的定制私有 Python 包,您可以从您的 Git 库 PIP 安装该包
- 面向绝对初学者的虚拟环境——什么是虚拟环境以及如何创建虚拟环境(+示例)
- 通过简单升级,显著提高数据库插入速度
- 在这里阅读更多关于py-console和更多关于 colorama 这里 。
编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟我来!