TowardsDataScience 博客中文翻译 2020(三百一十六)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

字典作为 If-Else 的替代

原文:https://towardsdatascience.com/dictionary-as-an-alternative-to-if-else-76fe57a1e4af?source=collection_archive---------5-----------------------

使用字典创建一个更清晰的 If-Else 函数代码

动机

您可能经常使用 Python 的字典。但是,您是否已经释放了字典的全部容量来创建更高效的代码呢?如果你不知道如何创建一个有序的字典,将多个字典组合成一个映射,创建一个只读字典,你可以在这里找到更多的。

本文将关注如何使用 Python 的字典作为 if-else 语句的替代。

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

图片来自 PixabayGerd Altmann

削减 If-Else 语句,增加默认值

假设我们有杂货店商品的价格表:

price_list = {
'fish': 8,
'beef': 7,
'broccoli': 3,
}

我们想打印商品的价格,但是预期不是每个商品都在price_list.中,所以我们决定创建一个函数:

def find_price(item):
    if item in price_list:
        return 'The price for {} is {}'.format(item, price_list[item])
    else:
        return 'The price for {} is not available'.format(item)>>> find_price('fish')
'The price for fish is 8'>>> find_price('cauliflower')
'The price for cauliflower is not available'

聪明。if-else 语句做了我们希望它做的事情:当项目不可用时返回另一个值。但是我们查询字典两次,并使用两条语句返回几乎相同的内容。我们能做得更好吗?如果条目不在列表中,有没有办法返回一个默认值?幸运的是,Python 的字典方法有一种方法可以做到这一点,叫做get()

def find_price(item):
    return 'The price for {} is {}'.format(item, price_list.get(
        item, 'not available'))

.get()查找一个键,用不存在的键返回默认值。代码看起来确实更短了,但是它的表现是否像我们想要的那样呢?

>>> find_price('fish')
'The price for fish is 8'>>> find_price('cauliflower')
'The price for cauliflower is not available'

整洁!

但是我可以用 Dict 来实现不涉及字典的功能吗?

好问题。让我们来处理一个完全不涉及字典的例子。

假设您想创建一个函数,返回两个数之间的运算值。这就是你得出的结论:

def operations(operator, x, y):
    if operator == 'add':
        return x + y
    elif operator == 'sub':
        return x - y
    elif operator == 'mul':
        return x * y
    elif operator == 'div':
        return x / y>>> operations('mul', 2, 8)
16

你可以利用字典和get()方法得到一个更优雅的代码:

def operations(operator, x, y):
    return {
        'add': lambda: x+y,
        'sub': lambda: x-y,
        'mul': lambda: x*y,
        'div': lambda: x/y,
    }.get(operator, lambda: 'Not a valid operation')()

操作符的名字变成了键,lambda有效地将函数压缩成字典的值。get()找不到键时返回默认值。让我们检查一下我们的功能:

>>> operations('mul', 2, 8)
16
>>> operations('unknown', 2, 8)
'Not a valid operation'

太好了!它像我们想要的那样工作。

那么我应该把 If-Else 语句切换到字典吗?

替代方案使代码看起来更干净。但是如果我们想使用字典,我们应该考虑这个开关的缺点。每次我们调用operations(),它都会创建一个临时字典和 lambdas,让操作码循环。这将降低代码的性能。

因此,如果我们想使用字典,我们应该考虑在函数调用之前创建一次字典。这个行为防止代码在我们每次查找时重新创建字典。这种技术当然不会在所有情况下都适用,但是在您的工具箱中有另一种技术可供选择将是有益的。

this Github repo 中,您可以随意使用本文的代码。

我喜欢写一些基本的数据科学概念,并尝试不同的算法和数据科学工具。你可以通过 LinkedInTwitter 与我联系。

如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:

[## 高效 Python 代码的计时

如何比较列表、集合和其他方法的性能

towardsdatascience.com](/timing-the-performance-to-choose-the-right-python-object-for-your-data-science-project-670db6f11b8e) [## 使用 Python 最大化您的生产力

你创建了一个待办事项清单来提高效率,但最终却把时间浪费在了不重要的任务上。如果你能创造…

towardsdatascience.com](/maximize-your-productivity-with-python-6110004b45f7) [## 如何使用 Lambda 获得高效的 Python 代码

lambda 对元组进行排序并为您创造性的数据科学想法创建复杂函数的技巧

towardsdatascience.com](/how-to-use-lambda-for-efficient-python-code-ff950dc8d259) [## cy thon——Python 函数的加速工具

当调整你的算法得到小的改进时,你可能想用 Cython 获得额外的速度,一个…

towardsdatascience.com](/cython-a-speed-up-tool-for-your-python-function-9bab64364bfd) [## 通过专门的字典实现提高您的效率

创建包含有序和只读条目的字典,为不存在的键返回默认值,等等

medium.com](https://medium.com/better-programming/boost-your-efficiency-with-specialized-dictionary-implementations-7799ec97d14f)

参考

巴德丹。“Python 的把戏。”2017

Python 中的词典理解

原文:https://towardsdatascience.com/dictionary-comprehensions-in-python-912a453da512?source=collection_archive---------21-----------------------

如何使用字典理解在 Python 中创建字典

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

天一马Unsplash 上拍照

创建字典

假设我们想用 python 从另一个可迭代对象或序列(比如一个列表)创建一个字典。例如,我们有一个数字列表,我们想创建一个字典来计算每个元素在列表中出现的次数。因此,我们可以让字典的键成为列表中的不同元素(或数字),它们对应的值等于特定元素(或数字)在列表中出现的次数。

我们可以使用 for 循环来创建这个字典,如下所示:

num_list = [1,1,7,3,5,3,2,9,5,1,3,2,2,2,2,2,9]count_dict = {}for num in num_list:
    count_dict[num] = num_list.count(num)print(count_dict)
# {1: 3, 7: 1, 3: 3, 5: 2, 2: 6, 9: 2}

注意,我们必须先创建一个空字典, count_dict 。然后,当我们使用 for 循环遍历 num_list 时,我们在 count_dict 中创建了 key:value 对。在遍历 num_list 时,该键将等于我们当前所在的数字,其对应的值等于 num_list 中该数字的计数。

count()是一个列表方法,它返回我们传入的值在列表中出现的次数。

词典释义

就像我们在 python 中有列表理解一样,我们也有字典理解。我们可以回忆一下,列表理解允许我们以一种非常简洁的方式从其他序列中创建列表。

这里有一个完整的列表理解教程

[## Python 中的列表理解

用 python 创建列表的更优雅、更简洁的方式

towardsdatascience.com](/list-comprehensions-in-python-28d54c9286ca)

嗯,我们也可以使用 python 中的字典理解来以非常简洁的方式创建字典。

{ key:key 的值,iterable 或 sequence 中的值}

例如,我们可以使用字典理解来创建上面的字典,如下所示:

num_list = [1,1,7,3,5,3,2,9,5,1,3,2,2,2,2,2,9]count_dict = {num:num_list.count(num) for num in num_list}print(count_dict)
# {1: 3, 7: 1, 3: 3, 5: 2, 2: 6, 9: 2}

就是这样!首先,我们使用花括号来确定我们想要创建一个字典(与使用括号的列表相反)。然后,当我们在 num_list 上循环时,我们创建如下的键:值对:num:num _ list . count(num),其中键是数字(或 num),它的值是那个数字在 num_list 中的计数。

有条件的词典理解

就像在 list comprehensions 中一样,我们可以在 for 循环后使用 if 语句将条件添加到字典理解中。

{ key:key 的值,iterable 或 sequence if 中的值}

例如,如果我们只想在字典中查找偶数,我们可以使用下面的代码:

num_list = [1,1,7,3,5,3,2,9,5,1,3,2,2,2,2,2,9]count_dict = {num:num_list.count(num) for num in num_list if num_list.count(num)%2==0}print(count_dict)
# {5: 2, 2: 6, 9: 2}

正如我们所看到的,我们在字典理解中的 for 循环之后添加了一个 if 语句。如果当前数字的计数是偶数,我们将相应的键:值对添加到字典中。否则,我们不会将键:值对添加到字典中。

[## 成为更好的 Python 程序员的三个概念

了解 Python 中的*和运算符,*args 和kwargs 以及更多内容

towardsdatascience.com](/three-concepts-to-become-a-better-python-programmer-b5808b7abedc)

额外收获——对字典进行排序

我们还可以使用 sorted()函数根据计数对字典进行排序,如下所示:

num_list = [1,1,7,3,5,3,2,9,5,1,3,2,2,2,2,2,9]count_dict = {num:num_list.count(num) for num in num_list}sorted_dict = dict(sorted(count_dict.items(), key=lambda x:x[1]))print(sorted_dict)
# {7: 1, 5: 2, 9: 2, 1: 3, 3: 3, 2: 6}

有关上述代码的完整解释,请查看 python 中的对象排序教程:

[## Python 排序的终极指南

如何在 python 中对列表、元组、字符串和字典进行排序

levelup.gitconnected.com](https://levelup.gitconnected.com/the-ultimate-guide-to-sorting-in-python-d07349fb96d5)

如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名媒体会员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你用我的 链接 注册,我会赚一小笔佣金。

[## 通过我的推荐链接加入媒体——卢艾·马塔尔卡

阅读卢艾·马塔尔卡的每一个故事(以及媒体上成千上万的其他作家)。您的会员费直接支持…

lmatalka90.medium.com](https://lmatalka90.medium.com/membership)

结论

在这个简短的教程中,我们首先回顾了如何使用一个 iterable 对象(比如一个 list)创建一个带有 for 循环的字典,其中我们必须先创建一个空字典。然后,我们学习了如何使用字典理解来以更简洁和 pythonic 化的方式创建同一个字典,而不必先创建一个空字典。然后,我们看到了如何使用 if 语句将条件添加到字典理解中。最后,我们使用 sorted()函数根据列表中数字的计数值对字典进行了排序。

Python 字典详解:Python 编程

原文:https://towardsdatascience.com/dictionary-in-details-python-programming-7048cf999966?source=collection_archive---------36-----------------------

举例学习在编程问题求解中使用字典

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

来源:费尔南多·埃尔南德斯

Dictionary 是 Python 编程语言中的一种复合数据类型。在某种程度上,它类似于列表。列表是元素的集合。字典是键、值对的集合。借助字典,许多编程问题的解决方案可以变得更简单、更简洁。在这里我要解释一下字典里所有重要的方法和一些例题的解题。

让我们定义一个简单的字典。

d = {'a': 1, 'b':5, 'c': 3, 'd': 2, 'e': 8, 'f': 6}
  1. 在此字典中添加新元素:
d['s'] = 12
print(d)

字典 d 现在是这样的:

{'a': 1, 'b': 5, 'c': 3, 'd': 2, 'f': 6, 's': 12}

2.从字典 d 中删除元素 e。

del d['e']

3.获取键 a 的值。

d['a']
#Output: 1

4.“a”的值看起来太小了。将元素 a 的值更新为 10。

d['a'] = 10

5.将元素 s 的值加 3。

d['s'] += d['s'] + 3

6.检查字典 d 的长度。如果它小于 9,则向它添加三个以上的元素。

if len(d) < 8:
    d.update({'t': 21, 'h': 9, 'p':14})
print(d)
'''
Output:
{'a': 10, 'b': 5, 'c': 3, 'd': 2, 'f': 6, 's': 12, 't': 21, 'h': 9, 'p': 14}
'''

7.列出所有钥匙的清单。

d.keys()##Output looks like this:dict_keys([‘a’, ‘b’, ‘c’, ‘d’, ‘f’, ‘s’, ‘t’, ‘h’, ‘p’])

8.列出所有的值。

d.values()
##Output looks like this:
dict_values([10, 5, 3, 2, 6, 27, 21, 9, 14])

9.找出哪个字母的价值最大。

max = 0
max_key = 'a'
for k, v in d.items():
    if v > max:
        max_key = k
        max = d[max_key]
print(max_key)

答案出来是‘t’。

10.按升序对字典 d 的键进行排序。

sorted(d, key=lambda x: d[x])
#Output:
['d', 'c', 'b', 'f', 'h', 'p', 't', 's']

11.找出每个单词在下列句子中出现的次数。

sentences = "I love my country. My country is the best in the world. We have the best athletes in the world."

让我们制作一本字典,其中的关键字是这些句子中的单词,值是这些单词出现的频率。

以下是解决这个问题的步骤:

a.初始化一个空字典’ sen_map ’

b.把句子都变成小写

c.重复句子中的每个单词

d.检查 sen_map 中是否存在一个单词

e.如果不是,则添加值为 1 的单词

f.否则,将该单词的值更新 1

sen_map = {}  
sentences = sentences.lower() 
for i in sentences.split(): 
    if i not in sen_map:   
        sen_map[i] = 1  
    sen_map[i] += 1  
sen_map'''Output
{'i': 2,  'love': 2,  'my': 3,  'country.': 2,  'country': 2,  'is': 2,  'the': 5,  'best': 3,  'in': 3,  'world.': 3,  'we': 2,  'have': 2,  'athletes': 2}'''

字典可以像列表一样嵌套。这里有一个例子:

Gold_medals = {'USA': {'Wrestling': 3, 'Long Jump': 3, 'Basketball': 5},
              'China': {'Wrestling': 1, 'Long Jump': 5, 'Basketball': 3},
              'England': {'Wrestling': 2, 'Long Jump': 7, 'Basketball': 0}}

12.美国在跳远比赛中获得了多少枚金牌?

Gold_medals['USA']['Long Jump']

正如我们在上面的字典中看到的,输出是 3。

字典中组织信息的另一种方式是列表。

这里有一个例子:

students = [{'Name': 'John Smith', 'Age': 12, 'Score': 90},
           {'Name': 'Laila Jones', 'Age': 11, 'Score': 82},
           {'Name': 'Omar Martinez', 'Age': 10, 'Score': 70},
           {'Name': 'Romana Raha', 'Age': 13, 'Score': 78},]

13.返回班上得分最高的学生的姓名。

让我们一步一步解决这个问题。首先按降序对“学生”进行排序。

sorted(students, key=lambda x: x['Score'], reverse=True)
#Output:
'''[{'Name': 'John Smith', 'Age': 12, 'Score': 90},  {'Name': 'Laila Jones', 'Age': 11, 'Score': 82},  {'Name': 'Romana Raha', 'Age': 13, 'Score': 78},  {'Name': 'Omar Martinez', 'Age': 10, 'Score': 70}]'''

获取列表“学生”中的第一个词典。

sorted(students, key=lambda x: x['Score'], reverse=True)[0]'''Output
{'Name': 'John Smith', 'Age': 12, 'Score': 90}'''

最后,得到学生的名字。

sorted(students, key=lambda x: x['Score'], reverse=True)[0]['Name']

这一行代码将返回学生的姓名。也就是‘约翰·史密斯’。

我试图先解释字典的方法,然后给出一些例子来说明如何使用字典。我希望这有所帮助。

下面是字典的视频教程:

Python 3.9 中的字典联合运算符和新的字符串方法

原文:https://towardsdatascience.com/dictionary-union-operators-and-new-string-methods-in-python-3-9-4688b261a417?source=collection_archive---------49-----------------------

可以简化您的数据处理代码的新功能—它们是什么?它们改进了什么?

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

卡南·哈斯马多夫在 Unsplash 上的照片

Python 3.9 已经积累了一份长长的改进列表,其中有一些非常重要的变化,比如一种新型的解析器。与 LL(1)解析器相比,新的 PEG 解析器占用了更多的内存,但是速度更快,应该能够更好地处理某些情况。虽然,根据发布文档,我们可能从 Python 3.10 开始才能看到真正的效果。Python 3.9 还扩展了一些内置和标准库集合类型的使用,作为通用类型,而不必求助于typing模块。如果您使用类型检查器、linters 或 ide,这尤其有用。

也许,在处理文本数据的日常工作中最方便的两个特性是添加到dict中的联合操作符和新的string方法。

dict的联合运算符

dict引入了合并 |更新 |=两个联合运算符。

因此,如果您想基于您已经拥有的两个字典创建一个新的dict,您可以执行以下操作:

>>> d1 = {'a': 1}
>>> d2 = {'b': 2}
>>> d3 = d1 | d2
>>> d3
{'a': 1, 'b': 2}

如果您想用这两个字典中的一个来扩充另一个,您可以这样做:

>>> d1 = {'a': 1}
>>> d2 = {'b': 2}
>>> d1 |= d2
>>> d1
{'a': 1, 'b': 2}

在 Python 中已经有几种合并字典的方法。这可以使用[dict.update()](https://docs.python.org/3/library/stdtypes.html#dict.update)来完成:

>>> d1 = {'a': 1}
>>> d2 = {'b': 2}
>>> d1.update(d2)
>>> d1
{'a': 1, 'b': 2}

然而,dict.update()修改了您正在就地更新的dict。如果你想有一个新的字典,你必须先复制一个现有的字典:

>>> d1 = {'a': 1}
>>> d2 = {'b': 2}
>>> d3 = d1.copy()
>>> d3
{'a': 1}
>>> d3.update(d2)
>>> d3
{'a': 1, 'b': 2}

除此之外,在 Python 3.9 之前,还有 3 种方法可以实现这一点。合并两个字典也可以通过[{**d1, **d2}](https://www.python.org/dev/peps/pep-0448/)构造来实现:

>>> d1 = {'a': 1}
>>> d2  = {'b': 2}
>>> {**d1, **d2}
{'a': 1, 'b': 2}

但是,如果您使用的是dict的子类,例如defaultdict,这个构造将把新字典的类型解析为dict:

>>> d1
defaultdict(None, {0: 'a'})
>>> d2
defaultdict(None, {1: 'b'})
>>> {**d1, **d2}
{0: 'a', 1: 'b'}

工会运营商不会出现这种情况:

>>> d1
defaultdict(None, {0: 'a'})
>>> d2
defaultdict(None, {1: 'b'})
>>> d1 | d2
defaultdict(None, {0: 'a', 1: 'b'})

另一种将两个字典合并成一个新字典的方法是 dict(d1, **d2):

>>> d1 = {'a': 1}
>>> d2  = {'b': 2}
>>> dict(d1, **d2)
{'a': 1, 'b': 2}

然而,这只适用于所有键都是类型string的字典:

>>> d1 = {'a': 1}
>>> d2 = {2: 'b'}
>>> dict(d1, **d2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

第三种选择是使用 [collections.ChainMap](https://www.python.org/dev/peps/pep-0584/#id15)。这可能比前两种方法更不直接,而且不幸的是,如果您更新链图对象,会修改底层字典:

>>> d1 = {'a': 1, 'b': 2}
>>> d2 = {'b': 3}
>>> from collections import ChainMap
>>> d3 = ChainMap(d1, d2)
>>> d3
ChainMap({'a': 1, 'b': 2}, {'b': 3})
>>> d3['b'] = 4
>>> d3
ChainMap({'a': 1, 'b': 4}, {'b': 3})
>>> d1
{'a': 1, 'b': 4}
>>> d2
{'b': 3}

用于dict的新的合并和更新 union 操作符解决了这些问题,看起来没有那么麻烦。但是,它们可能会与按位“或”和其他一些运算符混淆。

移除前缀和后缀的字符串方法

正如 PEP-616 文档所建议的,几乎任何时候你使用str.startswith()str.endswith()方法,你都可以简化代码如下。

以前,如果您正在规范化一个列表,例如,电影标题,并从无序列表中删除任何额外的标记,如破折号,您可以这样做:

>>> normalized_titles = []
>>> my_prefixed_string = '- Shark Bait'
>>> if my_prefixed_string.startswith('- '):
...     normalized_titles.append(my_prefixed_string.replace('- ', ''))
>>> print(normalized_titles)
['Shark Bait']

然而,使用str.replace()可能会产生不必要的副作用,例如,如果破折号是电影标题的一部分,而您实际上更愿意保留它,如下例所示。在 Python 3.9 中,您可以安全地这样做:

>>> normalized_titles = []
>>> my_prefixed_string = '- Niko - The Flight Before Christmas'
>>> if my_prefixed_string.startswith('- '):
...     normalized_titles.append(my_prefixed_string.removeprefix('- '))
>>> print(normalized_titles)
['Niko - The Flight Before Christmas']

PEP 文档指出,用户报告期望来自另一对内置方法[str.lstrip](https://docs.python.org/3/library/stdtypes.html#str.lstrip)[str.rstrip](https://docs.python.org/3/library/stdtypes.html#str.rstrip)的这种行为,并且通常必须实现他们自己的方法来实现这种行为。这些新方法应该以更健壮和有效的方式提供该功能。用户将不必关心空字符串的情况或使用str.replace()

另一个典型的用例如下:

>>> my_affixed_string = '"Some title"'
>>> if my_affixed_string.startswith('"'):
...     my_affixed_string = my_affixed_string[1:]
...
>>> if my_affixed_string.endswith('"'):
...     my_affixed_string = my_affixed_string[:-1]
...
>>> my_affixed_string
'Some title'

在 Python 3.9 中,这可以简化为:

>>> my_affixed_string = '"Some title"'
>>> my_affixed_string.removeprefix('"').removesuffix('"')
'Some title'

bytesbytearraycollections.UserString也宣布了这些方法。

Python 3.9 还有一系列有趣的模块改进和优化——一定要看看这些改进和优化,找出对您的应用程序最有用的。享受新功能!

如果您对 Python 感兴趣,请随意查看我关于并发性和并行性的文章。

30 天的自行车旅行让我更健康了吗?

原文:https://towardsdatascience.com/did-a-30-day-bicycle-tour-make-me-more-fit-639ae92cfd27?source=collection_archive---------60-----------------------

如果是的话,这能被测量吗?

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

TLDR:是的,它让我更健康。向下滚动以查看动画和导出的方程式。

上个月,我完成了奥特罗阿之旅——一次穿越新西兰的 3000 公里自行车打包之旅。完成的截止日期是 30 天。我从 2 月 21 日开始,到 3 月 23 日结束——29 天 23 小时 45 分钟后。于是我结束了 30 天的旅行,只剩下 15 分钟。

我知道这次旅行会是一次挑战。虽然我总体上很健康,也进行了一些自行车打包旅行,包括在 GDMBT 上的 2 周,但在过去几年里我的体重增加了一些,没有时间进行必要的训练(建议是 3000 公里,我可能完成了 200 公里?).

当我在头几天挣扎的时候,来自巡回赛老兵的建议通常是“别担心。你会变得更健康。训练成这样!”从主观上来说,随着巡演的进行,事情似乎变得稍微容易了一些。然而,实际上骑行 3000 km 后变得更健美了吗?我的身体在巡演结束时比开始时更好吗?如果是的话,我能测量吗?

重要统计数据

  • 32 岁
  • 生来就是男性
  • 身高 182 cm
  • 中度但可控的哮喘
  • 约 55 静息心率(午夜)
  • 骑着 2017 款铝制 Salsa Timberjack,配有 29er 车轮,13.5 kg。当然,我有很多装备,所以我们假设总共 25 公斤?

数据收集

在游览期间,我戴了一只佳明菲尼克斯 5 手表。这记录了我每隔几秒钟的距离、高度和心率。每天结束时,收集的数据会被同步到 Garmin 的服务器上。后来,我能够以 XML 格式下载这些数据。在这 30 天里,我总共检索了 187,216 个数据点。

数据概述

下面是基本的距离和高程数据。有一些差异需要注意。在第四天,我的变速器坏了,我被困在等待一个新的。然后,大约从第 20 天开始,我生病了。四天后,在第 24 天的另一个休息日达到了高潮。

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

适合度的度量

我的中学体育老师对健康有一个很好的定义。用他的话来说,想象两个同样身高体重的跑步者在 5k 比赛。在整个比赛中,没有一个运动员领先于另一个。然后他们在同一时间结束比赛。一名跑步者的平均心率为 150,另一名为 155。谁是更健康的跑步者?

答案当然是心率较低的跑步者。如果你的心脏在同样的输出量下工作得更少,你就更健康。骑自行车也是如此。事实上,如果我变得更健康,我会预计我的心率会在旅行过程中下降。那是真的吗?

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

看看我在旅游过程中的平均日心率,它确实似乎在下降。但是我的心率下降是因为我变得更健康了还是因为我变得更懒了?要回答这个问题,我需要将我的心率与我的努力程度进行比较。

清理数据

Garmin XML 文件提供了四个数据点:时间、距离、海拔和心率。这些是某个时间点的快照。为了让这些数据有用,我需要速度和坡度(比如上坡/下坡)。为此,我计算了时间间隔。例如,速度是距离随时间的变化。

处理心率时,还有一个额外的重要考虑因素。人类的心脏实际上需要时间来适应身体当前的努力水平。因此,计算出的时间间隔必须足够长,以捕捉这种调整。不过,不会太久的。间隔太长会有模糊数据的风险。通过实验,我发现 90 秒的间隔效果最好(或者至少给了我最好的 r !).

我做过的其他事情:

  • 删除了所有速度为 30 公里/小时的区间<5km/hr or >(要么是我站着不动,要么是 GPS 变得古怪)
  • 放弃所有大于 1 米/秒的爬升。同样,任何超过 1 米/秒的速度都远远超过了我的身体极限,这是因为古怪的 GPS 读数
  • 取消所有下降间隔。重力——而不是肌肉——是下降的主要力量。它们对这个分析没有用。

天真的第一遍

作为第一步,当我比较心率和速度时,我得到一个向下倾斜的趋势。这是为什么呢?为什么我走得越快心率越慢?

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

事实证明,当我慢下来的时候,很有可能我实际上是在爬山,并且更加努力地工作*。事实上,速度与成绩的关系图揭示了两者之间的密切关系。*

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

在热图上包括心率表明心率在分布的顶端附近最高。这是我快速上坡的时候。这是有道理的。

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

恒定输出模型

那么,在旅行的过程中,我变得更健康了吗?对于相同的输出,我的心跳变慢了吗?如果我将速度和坡度都保持在 8-12 公里/小时和 3-5%的坡度,我会发现一个明显的下降趋势。根据下面的动画估计,我的心跳在第 30 天比第一天慢了 20 bpm!

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

调整和多变量模型

动画很好,但我能做得更好吗?我可以使用我所有的数据,而不仅仅是 8-12 公里/小时,3-5%的坡度范围吗?我能得到精确的数字吗?我当然可以。我可以用一个多元模型

*heartrate = a*speed + b*grade + c*day*

通过 sklearn 的 线性回归 实现,对于所有 89238 个区间,我得到 r 为 0.27 。使用计算出的系数可得出以下等式…

*heartrate = .77*speed + 344.49*grade - 0.50*day + 116.61*

我还能调整什么?您可能会从动画中注意到,前三天似乎是数据中的异常值。造成这种情况的一个因素可能是我在头三天换了不同的轮胎。当我在第四天修好我的换档器时,我也认为我所用的轮胎,enduro 轮胎,太重了,所以我更换了它们。移除前三天的数据后,r 在处大致相同。26 新的等式如下

*heartrate = .83*speed + 365.81*grade - 0.19*day + 116.61*

*查看“天”的系数,我看到-0.19。**在旅程中的每一天,同样强度的运动,我的心率会下降 0.2 次/分钟。这意味着总共提高了 6 BPM!*这不像动画中暗示的那样戏剧化(~20BPM),但更精确,仍然是一个好结果!

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

开放数据

我的代码和原始数据可以在 Github 上获得。感谢阅读!

原载于**

脸书预言家让时间序列建模太容易了吗?

原文:https://towardsdatascience.com/did-facebook-prophet-make-time-series-modeling-too-easy-7d22a2bbf5dd?source=collection_archive---------47-----------------------

顶点工程的困境

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

图片由 kues1Adobe Stock

我最近选择库存需求预测作为我完成的一个为期 10 个月的数据科学项目的顶点项目(在熨斗学校)。我很高兴探索这个话题,因为我觉得它可以应用到我职业兴趣的几个不同领域。然而,当我开始应用脸书先知的项目时,我不禁觉得这太简单了。

我不禁想知道我是否做错了什么,选择了一个基本上为我做了工作的包。

我是不是作弊了?这个项目会让我更像一个分析师而不是数据科学家吗?

幸运的是,当我的导师向我保证数据科学家的主要目的是解决商业问题时,我的恐惧被平息了,我成功地做到了。我们使用不同的机器学习工具来解决这些问题。因此,如果一个工具碰巧有效地解决了这个问题,这是一件好事——它不必很复杂。我实际上节省了时间(在工作中,这相当于时间和金钱)。干得好,

经过进一步的研究,我发现脸书设计了这个软件包,以便分析师可以进行时间序列预测。除了数据科学家。时间序列建模可能是行业中一个特定的专业领域,因此并不是每个数据科学家都熟悉它。

脸书使数据科学家和分析师能够像有经验的时间序列建模者一样,高效、大规模地制作时间序列模型。我在这里确认(不是说他们需要我的确认)他们确实成功了。

他们称这是他们的大规模预测分析方法,他们说这种方法充分利用了人工和自动化任务。

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

摘自脸书先知的白皮书

我的没有超参数调整的基线模型能够准确预测测试数据,只有 8.65%的 MAPE(平均绝对百分比误差)。然后,当通过按照 Prophet 文档的指示执行网格搜索来调整两个超参数(变点和季节性先验尺度)时,我能够将模型改进为仅 6.47%的 MAPE。这在产品(库存)需求预测中被认为是极好的。

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

作者图片

他们的文档是我见过的最好的文档之一;同时为 R 和 Python 提供了一个组织良好的关于大多数模型使用的教程。为了更深入的探究,他们甚至包括了到他们白皮书的链接,该白皮书解释了模型背后的计算,并展示了更多潜在的使用方法。

总的来说,脸书似乎已经成功地创建了一个开源的时间序列建模包。这使得新的和经验丰富的数据科学家以及用 Python 和/或 R 编写代码的分析师现在可以有效地大规模建模和预测时间序列数据。

高效地解决业务问题总是一件好事。

你试过脸书先知的时间序列建模包吗?你觉得怎么样?我期待在评论中听到你的回应。

感谢您的阅读!我希望你喜欢。

参考资料:

我捅了马蜂窝了吗?

原文:https://towardsdatascience.com/did-i-kick-the-hornets-nest-7d26860a2638?source=collection_archive---------27-----------------------

回应对我的《厌恶模糊》一文的评论

继我的文章 “什么是暧昧厌恶?” 发表后,我收到了几个脾气暴躁的人顽强地为自己的选择行为辩护的评论。暗示数学人可以表现得不理性,类似于踢马蜂窝。

我欠这些评论一个回应,所以让我在这里谈谈主要的味道:

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

  • 原装* 安全摄像头?

点评:不能说不理智就此罢休!
回应:我绝对同意。我不会就此罢休的。请放心,这只是一个很长的系列文章中的一篇。被关在家里让我幻想了 5 年的实验/行为经济学和神经经济学(那些茂盛的研究生院草坪!)并激励我重新拾起那些话题。这篇文章并不是要把所有的 5 年都塞进一篇文章里——后面还有很多呢!

评论:称我的选择为“非理性”可不太好。我被冒犯了。
回应:“非理性”这个词在这里并不是一种诋毁……理性在经济学中有一个正式的定义,A & D 选择偏离了它。事实上,知道“非理性”和“次优”不是一回事,你会松一口气——非理性有时比理性更好。如果这个想法让你热血沸腾,你可能会爱上实验/行为经济学领域!这几乎是整个领域的内容,它为你提供了几十年的美味研究来咀嚼。此外,这篇文章仅仅展示了你失去理性的许多方式之一。还有好多好多!

点评:你忘记了风险厌恶和预期效用理论
回应:不,那些是不可能忘记的。但是你是对的,我应该在发表这篇文章之前写下他们。我的过失。别走开,我已经有一半的草稿了。当你在愤怒地搜索的同时,要小心避免混淆风险和模糊风险的新手错误。风险厌恶并不能解释模糊厌恶,但是在我润色这篇文章解释为什么会这样的时候,你必须对我有耐心。

***点评:人们选择一个& D 来对冲他们的赌注。***回应:没有,请再查一下游戏规则。一个早期的打字错误让它听起来像是你花 100 美元得到了两次机会(你只有一次机会!)但这在大部分投票结果出来之前就已经解决了。我们还是把 A & D 看做最喜欢的选择。(在允许两次出手的情况下,你很聪明地注意到了对冲机会,但对冲不足以解释结果。B & C 不受欢迎的问题仍然存在,因为 B & C 也提供了对冲机会。如果这种行为仅仅是为了对冲,为什么会有差异呢?)

***评论:这是两人博弈,所以纳什均衡…***回应:不,这是单人博弈。你不是在和我玩游戏,因为我不是想为自己赢得或保留这 100 美元,我是想在我想象的客厅里玩一个小游戏。但不用担心,我们可以在未来的话题上一起掀起一场博弈论风暴。

评论:你想骗我。回应:哎呀,我绝对不会。我很好。❤

评论:不,说真的,你在骗我。回应:你确实提出了一个非常相关的方法论观点。

在实验经济学中,实验者永远不要欺骗参与者,这一点非常重要。如果我们说桶里有三种颜色的高尔夫球,那就是桶里的东西。如果我们说这些球的颜色是在游戏开始时决定的,我们就不会在你身上做换手的戏法。如果你没有赢,我们也不会保留我们“存”的任何钱,所以我们不会和你玩。

我们不是试图诱导异常行为,而是试图揭示现有的行为。因此,欺骗任何人去做他们在野外类似情况下不会做的事情,这违背了我们的最大利益。(参与者事后可能会对他们的选择感到惊讶,这是另一回事。)

在实验室中,我们竭尽全力解释(口头和在同意书上)参与者看到的就是参与者得到的。他们选择什么取决于他们自己。回报是真实的,没有欺骗。因为实验经济学是我接受培训的传统,所以我也不想欺骗你。但我并不惊讶你认为我是。

我们的传统与那些信任被其他领域污染的参与者斗争,在这些领域,欺骗是乐趣的一部分。(我没有指责任何人,没有,根本没有,我不敢指责任何,咳咳,社会心理学实验室对参与者撒谎。)最后,实验经济学实验室最终会花费大量预算来修复这种破碎的信任(例如,进行几轮练习,如果参与者赢了,就把真钱放到他们手里)。

所以,很公平。

备注:【在此插入关于方差减少的备注。】
回应:诶,不是,没有分布就没有定义方差。你必须去贝叶斯继续这个思路,这让我…

评论:【此处插入贝叶斯评论。】
回应:贝叶斯哥们,你是不是在这里挣扎着攀越我们的语言障碍?将行为经济学家所谓的风险与不确定性映射到你的行话上:
客观风险涉及带有“客观”先验的概率计算。如果“客观”这个词激怒了你们当中那些在认识论上吹毛求疵的人,那就用“填鸭式”这个词来代替它。这些先验要么是由某人口述给你的(负责的决策者,给你布置家庭作业的教授,传统等等)。)或者也许他们是每个人都同意的先验知识(例如,公平对待硬币,没有争吵地前进)。
主观风险涉及你必须选择的先验概率计算。含糊不清包括感觉自己无力首先提出先验/假设。在特定情况下,区分模糊性和主观风险的警戒线因人而异。在这篇文章中,“经济学家”的角色在埃尔斯伯格的环境中从来不会面临模糊厌恶,因为他们被训练以一种跳过模糊直接面对主观风险的方式思考。这并不是因为他们对模糊厌恶免疫,而是因为他们不认为埃尔斯伯格的环境是模糊的。

经济学家被教导应用无差别原则。如果我们有幸有教授让我们练习,我们许多人会近乎本能地学会练习。感谢 雅各布·谭 为我写了一个 解释 什么的,这样我就不用说了。

也许你会发现,从决策者在被迫提供先验信息时感到的一种痛苦的角度来看待模糊厌恶现象是有用的。我不是在说挑选插入共轭先验的参数的苦恼,我是在说提出覆盖整个设置的假设。双主修贝叶斯统计和行为经济学的学生可能会通过预先启发的难度,从量化歧义子类的尝试中挤出一篇论文…

***点评:只是一个思想实验;如果真的涉及到金钱,人们就不会选择 A & D。*回应:伟大的本能,但不是,我的本科导师和最喜欢的教授是约翰·李斯特。各方面都很可爱,但在一个问题上是个暴君:不切实际的实验设置。这是有道理的,因为他是在经济学中率先使用现场实验的人。约翰的声音还活在我的脑海里,“如果是思想实验,不算,拿去实验室。但是你还没完!如果是实验室实验,不算,带去实地(真实世界)。始终在现实环境中研究有真实回报的选择行为。”多亏了像约翰这样的实地实验者,现代决策理论的大部分支柱——包括这一个——已经在现实世界中用真钱完全复制了。(在你问之前,是的,他们确实想到要改变奖励金额。)

现在是完全不同的东西…

感谢阅读!如果你在这里玩得开心,并且对人工智能感兴趣,这里有一个初学者友好的介绍供你娱乐:

在这里欣赏整个课程播放列表:bit.ly/machinefriend

与凯西·科兹尔科夫联系

让我们做朋友吧!你可以在 TwitterYouTubeLinkedIn 上找到我。有兴趣让我在你的活动上发言吗?用这个表格联系。

利物浦值得赢得英超冠军吗?

原文:https://towardsdatascience.com/did-liverpool-deserve-to-win-the-premier-league-eda7a4e1b9ca?source=collection_archive---------32-----------------------

使用 Python、R 和预期目标的英超赛季回顾

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

肖恩·唐纳利(@ 肖恩尼斯)在 Unsplash 上的照片

我转念一想,见日光之下,跑得快的不一定能赛跑,强壮的不一定能打仗,智慧的不一定能吃饭,聪明的不一定能发财,有才能的不一定能得宠。但是时间和机遇会降临到他们身上。

传道书 9:11

我期待圣诞礼物

如果一支足球队大规模超水平发挥,以绝对优势赢得联赛冠军,会发生什么?我们怎么知道他们是靠运气赢的,还是他们只是比其他人好得多?

体育分析行业在过去 10 年中开发的评估足球表现的指标之一是传说中的预期目标(xG)。这个想法是,进球只是可能发生的结果之一(射门击中横梁,守门员扑救,射门偏出,犯规,等等)。).现在我不想过多地讨论 xG 的定义(FbRef 有很好的解释和计算方法这里是,因为对于什么是 xG,或者如何计算 xG 有很多不同的观点。

为了简单起见,让我们把 xG 看作是一个球员射门(或射门机会)有多好的指标。有 1 比 1 机会对抗门将的球员?xG 高。一名中场球员在 40 码外射门越过人墙。低 xG。处罚?xG 高。在角旗附近拍摄的照片?低 xG。

几年来,职业足球俱乐部一直在使用 xG 来跟踪表现,而不是简单地通过查看最终结果来得出结论。它慢慢地走向媒体来帮助为辩论提供信息——尽管在与某些专家对话时可能会尽量避免。

现在我们已经建立了 xG,这个项目的目标(不是双关语)是确定球队在 2019/20 英超赛季的表现如何,相对于他们的最终位置——*具体来说,*利物浦是否应该成为赢家。为此我们需要一些东西:

  1. 本赛季英超联赛的 xG 数据
  2. 一个足球结果概率模型(主场胜利、平局、客场胜利)
  3. 花式图表

Scrapy

首先,让我们得到 xG 数据。FbRef 是一个很棒的开源网站,它们由 StatsBomb 的模型提供支持(该模型被认为是业界最好的模型之一)。在上面的链接中有两种获取 xG 数据的方法,第一种是下面的方法,它使用 Python 中的 Scrapy。在xg_spider.py中:

现在在终端中键入以下内容:

scrapy crawl expectedGoals --output exG.csv

让我们清理 R 中的数据(我知道我们可以用 Python 来做这些,但是我已经准备好了一个 Scrapy scraper,并且我的大部分模型已经在 R 中了):

install.packages("tidyr")prem1920<-read.csv("exG.csv")#get rid of empty rows
prem1920<-prem1920[rowSums(is.na(prem1920)) == 0,] #separate delimiter by hyphen into two columns
tidyr::separate(prem1920, score, into = c("homeScore", "awayScore")) 

Et voila,你现在有了一个带英超 2019/20 xG 数据的 csv!

或者……您可以轻松地使用 Microsoft Excel 复制并粘贴这些表格,然后手动将数据整理成 csv 格式。

足球模型

好吧,这就是开始有趣的地方。足球比分可以用泊松分布来近似。我不想赘述,因为网上有大量的文献,如果你想查看描述近似过程的 Python 或 R 代码,这是一个很好的网站

无论如何,我们可以使用内置泊松包中的 R 来计算一场足球比赛的正确得分,给定两队的得分率。打开一个名为FootballMatchResult.R的文件:

这个简单的泊松模型实际上并不像你希望的那样符合数据,因为低分和抽牌概率比模型更有可能,但是…出于时间的考虑,让我们改天再讨论这个问题。

使用这个模型,我们现在可以推导出一场比赛中球队的期望得分(xPts)。例如,在第一场比赛(利物浦对诺维奇城)中,利物浦的得分为 1.9,诺维奇城的得分为 1.0——现在假设一个球队赢了 3 分,平了 1 分,我们可以通过键入以下内容得出比赛结果概率

FootballMatchResult(1.9,1.0)

到 R 控制台,它会以向量的形式输出主场赢、平局和客场的概率:

[1] 0.5840664 0.2210540 0.1948797

因此,如果我们想计算利物浦的 xPts,我们将第一个条目乘以 3,然后加上中间的条目。诺里奇城也是如此,但在他们的例子中,将最后一个条目乘以 3,再加上中间的一个。

排行榜不会说谎

让我们将每场比赛从prem1920数据框计算到 xPts 表中,看看各队在比赛中根据 xG 表现如何。我们将总结三个属性,并将它们整理成一个排名表。这些属性是:期望得分(xPts)、期望进球得分(xG_For)和期望进球得分(xG_Against)。这是代码

随着 RStudio 的魔力,开火

xPts<-ExpectedPointsTable(prem1920)

进了候机楼。我们现在能够导出这个 xPts 表:

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

2019/20 英超 xPts 表

与实际的最终排名相比:

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

2019/20 英超决赛表

它回家了

好吧,我们从哪里开始?让我们首先回答这个问题,利物浦今年是否值得赢得联赛冠军。对于强大的红军来说,可悲的是,根据 xPts 表,答案是响亮的。根据 xPts,他们甚至没有排在第二,根据 xG,切尔西应该排在他们前面!

曼城今年的表现绝对让所有人大吃一惊,在对阵 93.1 xG 的比赛中打入 102 球,并让他们的失球数量几乎达到了预期(35 球对 35.2 球)。他们的 xPts 也相当准确,81.89,而实际得分是 81。领先第二名超过 10 分钟!佩普瓜迪奥拉可以认为他的球队今年非常不走运——但是嘿,他们推翻了欧洲冠军联赛的禁令,所以摇摆和回旋。

排名前四的球队是,嗯,排名前四的球队——除了曼联可能应该获得第四名而不是第三名。然而,xPts 的排名显示他们的防守在联赛中仅次于同城对手,所以也许大卫·德·赫亚和哈里·马奎尔并没有那么差

尽管莱斯特城在赛季的最后一天错过了一个冠军联赛席位,但 xPts 表表明他们度过了伟大的一年,并最终完成了他们应该完成的任务。不幸的是,热刺就不一样了。他们获得了第六名,但是 xPts 的排名表显示他们甚至不是一个顶级球队!何塞·穆里尼奥似乎有很多事情要做来改善他的球队(以及他自己的声誉)…

运气好不如运气好

现在我们讨论垫底的队伍。沃特福德肯定感到特别不公平——他们为了远离倒数三名已经做得够多了。根据 xPts 排名表,伯恩茅斯是另一支感觉今年应该保持领先的球队。

诺维奇城的表现可能也需要更多,尽管可能不足以在英超联赛中再生存一年。如果我是阿斯顿维拉、水晶宫或西汉姆招聘团队的教练或工作人员,我会回头看看,如果夏休期间没有任何变化,我会有点担心我的球队下赛季的前景。

然而,他们都不应该像纽卡斯尔联队那样担心。虽然他们非常符合他们的预期目标数字,但他们以最低的预期 xG 在 xPts 表中垫底。不知何故,史蒂文·布鲁斯的球队设法比他们应得的多得了 8 分,这足以将他们拉到安全地带。这正说明了保持领先和被降级之间的差距有多小!

书中还有最后一个技巧。我们可以基于 xPts 表使用 R 来模拟 1000 个季节(代码不包括在内,因为我发誓我家里的灯肯定已经开始闪烁,因为需要用我的样板代码来运行模拟人生)。这是基于 1000 个赛季模拟的最终位置热图(V1 =第一名,V2 =第二名,以此类推)。颜色越粉红,团队在特定位置完成的次数就越多:

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

英超模拟位置热图

曼城基本上巩固了第一的位置,几乎没有挑战者(利物浦和切尔西最有可能击败他们)。看起来在温格的中年时期,曼联变成了阿森纳,而阿森纳变成了……21 世纪初的热刺。在积分榜的另一端,纽卡斯尔联队牢牢地固定在垫底的位置,很少能在最后的 4 或 5 名之外。

真的让你觉得…

警告

虽然 xG 已经成为分析行业预测团队绩效的标准指标之一,但现在讨论它的局限性可能是明智的——以及这个项目的局限性。

首先,有几种定义 xG 的方法——似乎没有一家公司在计算 xG 的最佳方法上达成一致。一些分析师可能会根据机会的角度增加 xG 的权重,而一些分析师可能会比其他人更多地惩罚长镜头,等等。这样想吧——每个人都知道目标是什么。黄牌或角球也是如此。它们都是定义明确、切实可见的统计数据。但是什么是 xG 呢?在这被标准化之前,它将继续是一个问题。

接下来,每个模型都有局限性,我们天真的泊松足球模型也不例外。它假设比赛中的两个进球是独立的——例如,如果一个进球得分了,它不会影响结果。事实上,这只是无稽之谈-如果你是一名球员,如果你的比赛中有进球,你会改变方法。因此,一个在 1-0 落后的情况下追逐比赛的球队将会和一个有两个进球缓冲的球队表现不同。为了使模型更加有用,需要对模型中的依赖性进行一些计算。只要记住经典的足球陈词滥调- 进球改变比赛

最后,xG 真的是一个团队表现的重要指标吗?在其他限制中,该指标没有考虑到单个玩家(或他们的相对技能)。在一天结束时,大多数 xG 模型都是建立在成千上万次射门和机会的数据库上的——最终的输出将是一个平均值,而不是特定于玩家的。出于论证的目的,只要认为一个前锋,一般来说,在某些位置上应该比一个后卫有更好的射门率。那么可能会有一些球员比其他人更擅长把握某些类型的机会。这是指标开始分解的地方,理解这一点是充分利用 xG 的关键。

那么…利物浦值得赢吗?谁在乎呢,它还是发生了。到最后,不管发生了什么,唯一重要的还是结果。

其他文章

[## 运动中的随机性

为什么赢得超级碗的球队比英超多?

towardsdatascience.com](/randomness-in-sport-6e60c6132838) [## 创造机会

英国博彩业繁荣简史,从贝叶斯到必发

towardsdatascience.com](/making-up-the-odds-73fbc509ee7f)

锁定报告卡:新加坡

原文:https://towardsdatascience.com/did-singapore-obey-lockdown-rules-2aef5e55ad2c?source=collection_archive---------78-----------------------

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

艺术:陈卓琳

谷歌的社区流动性数据集揭示了新加坡“断路”措施对行为、服从和习惯的影响

为了遏制新冠肺炎病毒的传播,新加坡在 4 月份实行了为期两个月的局部封锁。政府“断路器”倡议的规则包括强制关闭几家零售企业,禁止公共集会,以及为老年人提供居家咨询。

从 2 月到 5 月,谷歌的社区移动数据集跟踪了不同地方的访问量相对于基线的变化——一月份的五周时间。谷歌使用相同的数据(来自选择分享谷歌位置历史的用户)计算这些变化,这些数据用于在谷歌地图中显示地点的流行时间

对数据集的分析显示了高水平的遵守,在断路器启动的当天,大多数户外场地的移动数据急剧下降。

断路器的直接影响

下图显示了对 4 月 7 日开始的断路器措施的即时反应。花在工作场所的时间几乎减少了三倍,花在家里的时间几乎增加了一倍。

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

蓝线(工作)和绿线(家庭)在 4 月和 5 月的波峰和波谷与周末和公共假日相对应,似乎不能解释任何异常行为。

杂货店/药店访问量的激增(黄线)与 5 月初断路器措施开始前的恐慌性购买新闻报道相对应。

3 月 17 日,邻国马来西亚进入封锁状态的消息也引发了更早的恐慌性购买事件。

公园和停车场

以下图表显示了前往五类地方的旅行数据:

杂货店&药房 —杂货店、食品仓库、农贸市场、特色食品店、药店和药房。

公园 —地方公园、国家公园、公共海滩、码头、狗公园、广场和公共花园。

公交车站 —地铁、公交、火车站等公共交通枢纽。

零售&娱乐 —餐馆、咖啡馆、购物中心、主题公园、博物馆、图书馆和电影院。

工作场所 —工作场所。

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

杂货店和药店受到的影响最小(与基线相差约 20%),因为它们仍被允许经营。而在 4 月 7 日之后,对零售、交通和工作场所的访问大幅下降,比基线下降了 60%以上。

参观公园的游客没有经历同样的急剧下降,因为这是断路器期间允许的“自由”之一。然而,政府将公园内的持续集会视为新冠肺炎聚集的潜在来源,并开始关闭停车场和公园内的特定区域,以确保符合社会距离措施。这导致公园的使用率慢慢下降,并最终在两周后降至-60 以下。

地区#家庭冠军

在这两个月里,新加坡可以称得上是呆在家里最多的地区。从全球来看,新加坡位列第三,仅次于巴拿马和斯里兰卡。

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

一个好的家庭记录并不一定意味着在新冠肺炎战场上的类似结果。没有实施封锁的台湾,其居民在家的时间只增加了 3%。它将新冠肺炎的死亡人数保持在一位数(相比之下,新加坡为 23 人)。

在家工作:共享的全球体验

我们最后的图表显示了 132 个不同国家的交通和工作场所使用的变化。除了台湾、莫桑比克和巴布亚新几内亚,样本中的所有其他国家的工作场所访问和交通使用都有所下降。

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

走向

随着新加坡开始放松封锁措施,下个月重新访问该数据集的更新版本将会很有意思。一个有趣的问题是,流动性——以及总体生活——是否会回到基线水平。

信用

文/数据分析— Samuel He | samuelhe@ntu.edu.sg

编辑:珀尔·李| pearllpy@gmail.com

艺术—谭卓琳|【instagram.com/fakejolart/】T2

数据集—谷歌有限责任公司*“谷歌新冠肺炎社区移动报告”*。
https://www.google.com/covid19/mobility/访问:【2020 年 5 月 30 日>。

探索更多

我为可能想探索其他国家类似趋势的读者建立了一个交互式 tableau 仪表盘

谷歌对这些数据的说法

每天的变化与一周中该天的基线值进行比较:

基线是 2020 年 1 月 3 日至 2 月 6 日这 5 周期间,一周中相应一天的中值。

数据集显示几个月的趋势,最近的数据代表大约 2-3 天前的数据,这是生成数据集所需的时间。

计算中包括哪些数据取决于用户设置、连接性以及是否符合我们的隐私阈值。如果没有达到隐私阈值(当某个地方没有忙到足以确保匿名时),我们不会显示一天的变化。因此,您可能会遇到某些地点和日期的空字段。

我们包括对社会距离努力以及获得基本服务有用的类别。

我们根据选择加入谷歌账户位置历史的用户的数据来计算这些见解,因此这些数据代表了我们的用户样本。与所有样本一样,这可能代表也可能不代表更广泛人群的确切行为。

识别内存消耗高的变量

原文:https://towardsdatascience.com/did-you-know-how-to-identify-variables-in-your-python-code-which-are-high-on-memory-consumption-787bef949dbd?source=collection_archive---------52-----------------------

优化 Python 代码

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

附身摄影Unsplash 上拍照

有时候,在执行 Python 脚本时,我们会遇到内存错误。这些错误主要是由于一些变量内存消耗高

在本教程中,我们将重点关注剖析 Python 代码以优化内存消耗内存分析是一个过程,使用它我们可以剖析我们的代码并识别导致内存错误的变量。

1。)内存分析器

  • 内存分析器是一个 python 包,它评估函数中编写的每一行 Python 代码,并相应地检查内部内存的使用情况。
  • 我们可以使用 pip 或者 conda 包管理器 来安装这个包。如果使用 Anaconda 发行版,我会推荐使用 conda install ,因为它会自动解决依赖和环境问题。
**#### Install Packages** conda install memory_profiler
  • 安装完成后,可以使用以下代码行在 iPython 环境中加载评测器:
**#### Loading Memory Profiler Package** %load_ext memory_profiler
  • 加载后,使用以下语法对任何预定义的函数进行内存分析:
**#### Memory Profiling a Function** %mprun -f function_name_only Call_to_function_with_arguments

:要使用内存评测器评测一个函数,将其作为一个模块导入,这意味着将其写入一个单独的 。py 文件,然后像其他包一样导入到主程序中。

2.)工作示例

对于本教程,我使用了与内存分析器的 PyPi 网页相同的功能。它创建两个有大量元素的列表变量,然后删除其中一个(参考下面代码中的函数定义)。然后我们保存并导入这个函数到主程序中进行分析。

**#### Function Definition (Saved as example.py file)**
def my_func(): 
    a = [1] * (10 ** 6) 
    b = [2] * (2 * 10 ** 7) 
    del b 
    return a**#### Loading the line profiler within ipython/jupyter environment** %load_ext memory_profiler
from example import my_func**#### Profiling the function using line_profiler**
%mprun -f my_func my_func()**#### Memory Profile Output**
Line #    Mem usage    Increment   Line Contents
================================================
     1     50.8 MiB     50.8 MiB   def my_func():
     2     58.4 MiB      7.6 MiB    a = [1] * (10 ** 6)
     3    211.0 MiB    152.6 MiB    b = [2] * (2 * 10 ** 7)
     4     58.4 MiB      0.0 MiB    del b
     5     58.4 MiB      0.0 MiB    return a

说明

从内存分析器的输出中,可以看到输出表有四列:

  • 第 1 列(行号) —代码行号
  • 第 2 列(内存使用) —函数使用的总内存,直到行号
  • 第 3 列(增量) —程序特定行使用的内存
  • 第 4 列(内容) —实际代码内容

注意:从程序中去掉 ***变量“b”***后内存消耗明显下降。

从表中可以清楚地看出, ***变量“b”***的创建导致了内存使用量的突然激增。在实时场景中,一旦我们意识到这样的发现,我们总是可以寻找其他方法来编写代码,使内存消耗保持在可接受的范围内。

结束语

在这个简短的教程中,我们学习了 Python 代码的内存分析。要了解 Python 代码的时间分析,请参考教程。

我希望这个你知道吗系列的教程信息丰富,足智多谋。

我们将在未来的教程中尝试引入更多有趣的话题。在此之前:

快乐学习!!!!

如何让您的 Python 代码运行得更快—第 1 期

原文:https://towardsdatascience.com/did-you-know-how-to-make-your-python-code-run-faster-1st-installment-f317359159a1?source=collection_archive---------20-----------------------

优化系统硬件的利用率

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

蒂姆·高在 Unsplash 上拍摄的照片

的上一篇教程中,我们向您介绍了 line_profiler ,这是一个可以帮助您对代码进行时间分析的包。现在是向前迈出一步的时候了。

在本教程中,我们将学习使用 Python 实现多线程和多处理方法。这些方法指导操作系统优化利用系统硬件,从而提高代码执行效率。

让我们开始吧…

多线程

引用 Wiki —在计算机架构中,多线程是中央处理器(CPU)(或多核处理器中的单核)提供多个线程并发执行的能力,由操作系统支持。

给定并发性,可以启动多个进程的并行执行,并实现更快的运行时。不去探究技术细节(谷歌和阅读 GIL ),要记住的一件事是多线程在执行基于 I/O 的任务(如下载图像和文件)时效率更高。另一方面,多处理更适合基于 CPU 的计算密集型任务。

Python 中的多线程实现

为了实现多线程,我们将使用 Python 的标准库,线程。默认情况下,标准 Python 安装附带了这个库,因此可以在我们的代码中直接导入。

为了演示多线程的有效性,我们将从 Unsplash 下载 5 幅图像。让我们观察顺序下载这些图像时的执行时间:

**#### Importing requests library**
import requests**#### Defining the function** 
def down_img(name,link):
    data = requests.get(link).content
    name = f"/home/isud/DidYouKnow/Tutorial 5/{name}.jpg"
    with open(name, "wb") as file:
        file.write(data)**#### 5 images downloaded sequentially**
%%timeit -n1 -r1
images = ['[https://images.unsplash.com/photo-1531458999205-f31f14fa217b'](https://images.unsplash.com/photo-1531458999205-f31f14fa217b'),
          '[https://images.unsplash.com/photo-1488572749058-7f52dd70e0fa'](https://images.unsplash.com/photo-1488572749058-7f52dd70e0fa'),
          '[https://images.unsplash.com/photo-1531404610614-68f9e73e35db'](https://images.unsplash.com/photo-1531404610614-68f9e73e35db'),
          '[https://images.unsplash.com/photo-1523489405193-3884f5ca475f'](https://images.unsplash.com/photo-1523489405193-3884f5ca475f'),
          '[https://images.unsplash.com/photo-1565098735462-5db3412ac4cb'](https://images.unsplash.com/photo-1565098735462-5db3412ac4cb')]
for i,link in enumerate(images):
    down_img(i,link)**#### %%timeit results**
51.4 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

可以看出,完整下载 5 个图像花费了 51.4 秒。此外,只有在前一次下载结束后,新的下载才会开始。现在让我们看看多线程如何提高代码性能。

**#### Importing necessary library** import threading
import requests**#### Defining the function** def down_img(name,link):
    data = requests.get(link).content
    name = f"/home/isud/DidYouKnow/Tutorial 5/{name}.jpg"
    with open(name, "wb") as file:
        file.write(data)**#### Images downloaded in parallel threads** %%timeit -n1 -r1
threads = []
images = ['[https://images.unsplash.com/photo-1531458999205-f31f14fa217b'](https://images.unsplash.com/photo-1531458999205-f31f14fa217b'),
          '[https://images.unsplash.com/photo-1488572749058-7f52dd70e0fa'](https://images.unsplash.com/photo-1488572749058-7f52dd70e0fa'),
          '[https://images.unsplash.com/photo-1531404610614-68f9e73e35db'](https://images.unsplash.com/photo-1531404610614-68f9e73e35db'),
          '[https://images.unsplash.com/photo-1523489405193-3884f5ca475f'](https://images.unsplash.com/photo-1523489405193-3884f5ca475f'),
          '[https://images.unsplash.com/photo-1565098735462-5db3412ac4cb'](https://images.unsplash.com/photo-1565098735462-5db3412ac4cb')]for i,link in enumerate(images):
    **t = threading.Thread(target=down_img, args=(i,link))**
    **t.start()**
    **threads.append(t)****for thread in threads:
    thread.join()****#### %%timeit results** 25.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

代码解释—定义图像下载循环:

  • 步骤 1(线程初始化) — Python 在单线程中运行完整的代码(姑且称之为主线程)。在这个例子中,通过从线程库中调用线程函数,我们启动并行线程并为它们分配一个要执行的目标进程(在这个例子中为 down_image )。被调用函数所需的所有参数都应该作为一个序列对象(在本例中为 tuple)传递。每次调用线程函数都会启动一个新线程(姑且称之为并行线程)。
  • 步骤 2(线程开始) —调用线程的 开始 方法将指示 Python 开始线程执行。假定for 循环的正在主线程中执行,并且函数调用在并行线程中执行,则在图像下载进行的同时,for 循环将继续执行。
  • 步骤 3(线程连接) —每个新线程都被捕获到一个名为 threads 的列表中。然后通过调用连接方法将并行线程连接到主线程。

为什么加盟是必要的?

直到第 2 步,我们所有的线程(主线程和并行线程)都处于并行执行状态。在这种情况下,主线程的执行可以在并行线程之前完成。为了避免这种情况,将并行线程连接到主线程是不可避免的。这将确保主线程的执行仅在并行线程完成之后完成。下图解释了这两种情况:

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

无连接和有连接的螺纹

可以看出,下载图像的执行时间缩短了近 50%(大约 25.6 秒)。).上述示例展示了多线程如何有助于 I/O 操作并提高下载/上传过程的效率。

多重处理

与在单个进程中执行多个线程的多线程不同,多线程为每个任务启动一个新的并行进程。如上所述,它为 CPU 密集型任务(需要大量计算的任务)提供了相当大的运行时间改进。

在 Python 中实现多重处理

多重处理是另一个支持 Python 多重处理特性的标准库。为了理解它的功能,我们将多次调用一个计算密集型函数。用于演示的函数计算 1 到 1000 万的平方。该功能并行执行 8 次。我们来观察一下这个函数在正常情况下的表现。

**#### Importing time library**
import time**#### Defining the function** 
def demo_func(num):
    for i in range(num):
        a = i**2**#### Calling demo function sequentially**
%%timeit -n1 -r1
for i in range(8):
    demo_func(10000000)**#### %%timeit results**
21.2 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

演示函数的顺序执行总共花费了 21.2 秒。现在,让我们检查一下在多处理设置中完成后的性能提升。

**#### Importing time library**
import time**#### Defining the function** 
def demo_func(num):
    for i in range(num):
        a = i**2**#### Multi-processing demo function** 
%%timeit -n1 -r1
processes = []
lop_size = [10000000,10000000,10000000,10000000,10000000,10000000,10000000, 10000000]
**p = multiprocessing.Pool()**
**p.map(demo_func,lop_size)**
**p.close()
p.join()****#### %%timeit results**
11.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

在多处理框架下,的执行时间下降了 50%,为 11.6 秒。在顺序处理中,一次使用一个单 CPU 内核,而在多处理中,并行使用所有系统内核。CPU 使用情况屏幕截图显示了同样的情况:

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

顺序执行与并行执行

上图中的每条线代表一个 CPU 内核。请注意,在顺序执行中,每个函数调用都会触发一个内核,而在并行执行中,所有内核都会被同时触发。

代码解释

  • 步骤 1(池创建)池方法创建可以并行利用的进程池。在没有任何参数的情况下,创建的进程数量等于系统上 CPU 内核的数量。我有一个四核系统(4 核),这意味着在执行 8 个函数调用时,前 4 个调用将并行运行,随后是后 4 个调用。请注意,您还可以在池中定义自定义的进程数量(比内核数量多**),但是超过这个数量**,它将开始消耗您的系统内存并可能降低性能
  • 步骤 2(池映射) —这是指示您的流程执行一个特定函数(第一个参数)以及要传递给它的一系列参数(第二个参数)
  • 步骤 3(池关闭)—关闭方法指示 Python 解释器,我们已经提交了我们想要提交到池中的所有内容,并且将来不会再有输入提供给池。
  • **第 4 步(池连接)——**与线程的情况一样,连接方法确保只有当所有并行进程完成时,代码执行才会完成。

从上面的场景中,我们可以看到多线程是如何成为对抗低效代码性能的强大武器的。

结束语

在本教程中,我们将重点放在通过优化利用系统硬件来提高代码性能上。在下一期文章中,我们将展示提高代码性能的具体编程实践。

我希望这篇教程是有启发性的,并且你学到了一些新的东西。

会在以后的教程中尝试并带来更多有趣的话题。在此之前:

快乐学习!!!!

如何让您的 Python 代码运行得更快—第 2 部分

原文:https://towardsdatascience.com/did-you-know-how-to-make-your-python-code-run-faster-2nd-installment-f105516cbd8a?source=collection_archive---------49-----------------------

高效的编程实践

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

Marc-Olivier Jodoin 在 Unsplash 上拍摄的照片

在前两个教程中,我们谈到了测量 python 代码执行时间的和优化硬件利用率以提高代码执行速度的。在本教程中,我们将再次致力于提高我们的代码运行时间,但是,通过使用一些编程最佳实践,如:

  • 记忆化
  • …向量化…

记忆化

a .)学校

我相信每个读这篇文章的人都会记得他们的小学时代。我们的数学老师总是记住从 2 到 20 的乘法表。想过为什么吗?很简单,这些表格构成了数学计算的基础,将它们储存在你的记忆中可以帮助你更快地计算。

b .)回到未来

记忆难懂的概念的旧概念通过名称记忆化被构建到现代世界编程概念中。**

引用维基百科,记忆化被定义为优化技术*,主要用于通过存储昂贵的函数调用的结果和在相同的输入再次出现时返回缓存的结果来加速计算机程序。***

c .)用 Python 实现

为了用 Python 演示这个概念,让我们首先定义一个昂贵的函数*。为了仿真一个昂贵的函数,这在计算上很耗时,我们将从时间包中调用睡眠函数,并人为增加函数的运行时间。*******

下面是创建该函数的 Python 代码。这个函数使用一个数值参数作为输入,休眠 5 秒钟,然后返回数值的平方作为输出。

*****USUAL APPROACH TO DEFINE A FUNCTION****#### Import time package**
import time**#### Defining expensive function - The Usual Way**
def expensive_function(n):
    time.sleep(5)
    return n**2***

d .)用常用方法运行时间

使用上面使用的方法,每次我们调用函数时,函数将首先休眠 5 秒钟,然后返回实际结果。让我们通过连续调用 3 次来对这个函数进行时间分析。

*****### Calling the expensive function 3 times and timing** 
%%timeit -n1 -r1
expensive_function(9)
expensive_function(9)
expensive_function(9)**### Time profile output** 15 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)***

假设,我们调用了该函数 3 次,记录的总执行时间为 15 秒

e.)使用记忆减少运行时间**

*****MEMOIZATION APPROACH TO DEFINE FUNCTIONS
### Importing package** import time**### Re-defining the expensive function** def expensive_function(n):
    **argument_dict** = {}
    if n in argument_dict:
        return argument_dict[n]
    else:
        argument_dict[n] = n**2
        time.sleep(5)
        return n**2**### Testing the run time** %%timeit -n1 -r1
expensive_function(9)
expensive_function(9)
expensive_function(9)**### Time Profile output**
5.01 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)***

解释

  • 由于记忆化提倡存储昂贵函数的结果,我们使用 Python 的字典来完成这项工作。注意字典" argument_dict "的定义**
  • 函数定义略有变化。现在,在休眠 5 秒钟并返回结果之前,该函数检查与传递的参数相关联的结果是否已经存在于字典中
  • 只有在内存(本例中为字典)中没有与传递的参数相关的结果时,才会调用 sleep 函数
  • 查看测试结果,可以注意到测试代码的执行时间(5.01 秒)比过去的执行时间(15 秒)减少了三分之一****

…向量化…

每当我们想到在代码中实现迭代操作时,我们想到的第一个编程结构就是循环*。当处理较小的数据集时,循环可以做得很好,但是随着数据集大小的增加,性能会下降。编程思维的一个小小的改变可以帮助我们克服这个挑战。***

考虑一个场景,我们需要对包含一百万行Pandas 数据帧的一列执行一些操作。如果以传统方式处理,将会一个接一个地循环一百万条记录。**

****另一方面,建议对整个列只执行一次该操作(而不是逐个记录)。这样,你就避免了执行一百万次同样的操作。下面的例子展示了我们如何从一些常见的编程场景中消除循环:

a)简单的数学运算

让我们考虑一个简单的场景,其中我们有一个包含 100k 直角三角形的底边垂直长度的熊猫数据帧,任务是计算它们的斜边*。计算公式如下:***

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

来源

让我们使用 pandas apply 方法(一个函数在一个系列对象上的迭代应用)来捕获执行该操作时的运行时间:

*****#### Importing libraries** import pandas as pd
import random**#### Creating Pandas with random values of base and perpendicular** base = [random.randint(1,100) for _ in range(100000)]
perpend = [random.randint(1,100) for _ in range(100000)]
triangle = pd.DataFrame(list(zip(base, perpend)),columns=["Base", "Perpendicular"])**#### Calculating hypotenuse** %%timeit
triangle["Hypotenuse"] = triangle.apply(lambda row: (row["Base"]**2 + row["Perpendicular"]**2) **(1/2), axis=1)**#### Performance** 3.43 s ± 52.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)***

使用 apply(熊猫循环最有效的形式之一)循环 100k 行花费了我们大约 3.43 秒。让我们看看矢量化如何帮助我们提高这段代码的性能。**

*****#### Importing libraries** import pandas as pd
import random**#### Creating Pandas with random values of base and perpendicular** base = [random.randint(1,100) for _ in range(100000)]
perpend = [random.randint(1,100) for _ in range(100000)]
triangle = pd.DataFrame(list(zip(base, perpend)),columns=["Base", "Perpendicular"])**#### Calculating hypotenuse** 
%%timeit
triangle["Hypotenuse"] = **(triangle["Base"]**2 + triangle["Perpendicular"]**2) **(1/2)****#### Performance** 5.81 ms ± 274 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)***

解释

  • 循环,在这个版本的代码中被删除了
  • 合并了对完整 pandas 系列对象(基本和垂直)操作的单个代码语句(以粗体突出显示)****
  • 与之前的 3.42 秒相比,整个操作在 5.81 毫秒内完成。这种改善难道不是实质性的吗?

b)涉及条件的操作

矢量化可以派上用场的另一个常见场景是,当循环被有条件地执行时。为了证明这一点,我们创建了一些虚拟数据,其中包含 2019 年我们家每小时的电力消耗信息(36524 条记录)。鉴于费用在一天的不同时间是不同的,我们想计算每小时的电费。费用表如下:*

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

编写简单循环时的性能评估:

*****#### Importing libraries** import pandas as pd**#### Importing Dataframe** df = pd.read_csv("/home/isud/DidYouKnow/Tutorial 4/electricity.csv")**#### Calculating Hourly Electricity Cost** %%timeit
df["cost"] = df[["Hour","Usage"]].apply(lambda x: x["Usage"]*5 if x["Hour"]<7 else (x["Usage"]*10 if x["Hour"] <19 else x["Usage"]*15),axis = 1)**#### Performance** 417 ms ± 19.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)***

使用熊猫最有效的循环方法(应用方法),我们可以在 417 毫秒内完成我们的目标。让我们评估使用矢量化执行相同操作时的性能:**

*****#### Importing libraries** import pandas as pd**#### Importing Dataframe** df = pd.read_csv("/home/isud/DidYouKnow/Tutorial 4/electricity.csv")**#### Calculating Hourly Electricity Cost** %%timeit
**less_seven = df["Hour"].isin(range(1,7))
less_nineteen = df["Hour"].isin(range(7,19))
rest = df["Hour"].isin(range(19,24))** df.loc[less_seven, "Cost"] = df.loc[less_seven, "Usage"] * 5 
df.loc[less_nineteen, "Cost"] = df.loc[less_nineteen, "Usage"] * 10 
df.loc[rest, "Cost"] = df.loc[rest, "Usage"] * 15**#### Performance** 7.68 ms ± 47 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)***

解释

  • 我们没有遍历每一行,而是为一天中不同的时间创建了逻辑向量,即 less_sevenless _ 19rest
  • 然后,这些逻辑向量用于成本构成的一次性(矢量化)计算。
  • 使用上述方法,我们可以将代码运行时间从的 417 毫秒减少到的 7.68 毫秒。**

结束语

在过去的 2 个教程中,我们已经尝试用编写高效代码的方法来武装你。尝试这些方法,让我们知道它们是否有帮助。

会在以后的教程中尝试并带来更多有趣的话题。在此之前:

快乐学习!!!!

你在 Spark SQL 中知道这个吗?

原文:https://towardsdatascience.com/did-you-know-this-in-spark-sql-a7398bfcc41e?source=collection_archive---------16-----------------------

入门

Spark SQL 中值得了解的 8 个不明显的特性。

Spark SQL 的 DataFrame API 是用户友好的,因为它允许用高级术语表达甚至非常复杂的转换。尤其是现在的 Spark 3.0,已经相当丰富和成熟了。然而,在某些情况下,您可能会发现它的行为出乎意料,或者至少不是很直观。这可能会令人沮丧,尤其是当你发现你的生产流水线产生了你没有预料到的结果。

在本文中,我们将介绍 Spark 的一些乍一看并不明显的特性,了解这些特性可以避免愚蠢的错误。在一些例子中,我们还会看到一些很好的优化技巧,根据您的转换,这些技巧会变得很方便。对于代码示例,我们将在 Spark 3.0 中使用 Python API。

1.array_sort 和 sort_array 有什么区别?

这两个函数都可以用来对数组进行排序,但是在用法和空值处理上有所不同。虽然 array_sort 只能按升序对数据进行排序,但是 sort_array 采用了第二个参数,在这个参数中,您可以指定数据应该按降序还是升序排序。 array_sort 会将空元素放在数组的末尾,当按降序排序时 sort_array 也会这样做。但是当以升序(默认)使用 sort_array 时,空元素将被放在开头。

l = [(1, [2, None, 3, 1])]df = spark.createDataFrame(l, ['id', 'my_arr'])(
    df
    .withColumn('my_arr_v2', array_sort('my_arr'))
    .withColumn('my_arr_v3', sort_array('my_arr'))
    .withColumn('my_arr_v4', sort_array('my_arr', asc=False))
    .withColumn('my_arr_v5', reverse(array_sort('my_arr')))
).show()+---+----------+----------+-----------+----------+-----------+
| id|    my_arr| my_arr_v2|  my_arr_v3| my_arr_v4|  my_arr_v5|
+---+----------+----------+-----------+----------+-----------+
|  1|[2,, 3, 1]|[1, 2, 3,]|[, 1, 2, 3]|[3, 2, 1,]|[, 3, 2, 1]|
+---+----------+----------+-----------+----------+-----------+

关于如何使用函数 array_sort 还有一个选项,即直接在 SQL 中使用(或者作为 SQL 表达式作为 expr() 函数的参数),其中它接受第二个参数,该参数是一个比较器函数(从 Spark 3.0 开始支持)。使用此函数,您可以定义如何比较元素来创建订单。这实际上带来了非常强大的灵活性,例如,您可以对结构数组进行排序,并定义应该按照哪个结构字段进行排序。让我们看看这个例子,在这个例子中,我们通过第二个 struct 字段进行显式排序:

schema = StructType([
    StructField('arr', ArrayType(StructType([
        StructField('f1', LongType()), 
        StructField('f2', StringType())
    ])))
])
l = [(1, [(4, 'b'), (1, 'c'), (2, 'a')])]
df = spark.createDataFrame(l, schema=schema)(
    df
    .withColumn('arr_v1', array_sort('arr'))
    .withColumn('arr_v2', expr(
        "array_sort(arr, (left, right) -> case when left.f2 < right.f2 then -1 when left.f2 > right.f2 then 1 else 0 end)"))
).show(truncate=False)

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

在这里,您可以看到 SQL 中表示的比较函数采用两个参数,它们是数组的元素,它定义了应该如何比较它们(即根据第二个字段 f2 )。

2. concat 函数不允许空值

concat 函数可以用于连接字符串,也可以用于连接数组。不太明显的一点是,该函数不允许空值,这意味着如果任何参数为空,那么输出也将为空。例如,当连接两个数组时,如果一个数组为空,我们很容易丢失另一个数组的数据,除非我们显式地处理它,例如,使用 coalesce :

from pyspark.sql.types import *
from pyspark.sql.functions import concat, coalesce, arrayschema = StructType([
    StructField('id', LongType()),
    StructField('arr_1', ArrayType(StringType())),
    StructField('arr_2', ArrayType(StringType()))
])l = [(1, ['a', 'b', 'c'], None)]
df = spark.createDataFrame(l, schema=schema)(
    df
    .withColumn('combined_v1', concat('arr_1', 'arr_2'))
    .withColumn('combined_v2', concat(coalesce('arr_1'), array(), coalesce('arr_2', array())))
).show()+---+---------+-----+-----------+-----------+
| id|    arr_1|arr_2|combined_v1|combined_v2|
+---+---------+-----+-----------+-----------+
|  1|[a, b, c]| null|       null|  [a, b, c]|
+---+---------+-----+-----------+-----------+

3.collect_list 不是一个确定性函数

聚合函数 collect_list 可用于在按某个键分组后创建元素数组,但它不是确定性的,因为结果数组中元素的顺序取决于行的顺序,而行的顺序在洗牌后可能不是确定性的。

了解优化器对非确定性函数的处理非常小心也是有好处的,例如,优化器不会对其进行筛选,如以下查询所示:

(
  df.groupBy('user_id')
  .agg(collect_list('question_id'))
  .filter(col('user_id').isNotNull())
).explain()

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

从计划中可以看到,过滤器是最后一个转换,因此 Spark 将首先计算聚合,然后过滤掉一些组(这里我们过滤掉 group,其中 user_id 为 null)。然而,如果数据首先被过滤器减少,然后被聚集,这将更有效,这确实会发生在确定性函数中,例如计数:

(
  df.groupBy('user_id')
  .agg(count('*'))
  .filter(col('user_id').isNotNull())
).explain()

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

这里的过滤器被推得更靠近源,因为聚合函数计数是确定性的。

除了 collect_list 之外,还有其他非确定性函数,例如 collect_setfirstlastinput_file_namespark_partition_idrand 等等。

4.对窗口进行排序会改变框架

有多种聚合和分析函数可以在所谓的窗口中调用,定义如下:

w = Window().partitionBy(key)

该窗口也可以通过调用 orderBy(key) 进行排序,并且可以通过 rowsBetweenrangeBetween 指定一个帧。这个框架决定了在窗口中调用哪个行的函数。一些功能也需要对窗口进行排序(例如 row_count ),但是对于一些功能,排序是可选的。关键是排序可以改变可能不直观的框架。考虑带有 sum 函数的示例:

from pyspark.sql import Window
from pyspark.sql.functions import suml = [
  (1, 10, '2020-11-01'), 
  (1, 30, '2020-11-02'), 
  (1, 50, '2020-11-03')
]df = spark.createDataFrame(l,['user_id', 'price', 'purchase_date'])w1 = Window().partitionBy('user_id')
w2 = Window().partitionBy('user_id').orderBy('purchase_date')(
  df
  .withColumn('total_expenses', sum('price').over(w1))
  .withColumn('cumulative_expenses', sum('price').over(w2))
).show()+-------+-----+-------------+--------------+-------------------+
|user_id|price|purchase_date|total_expenses|cumulative_expenses|
+-------+-----+-------------+--------------+-------------------+
|      1|   10|   2020-11-01|            90|                 10|
|      1|   30|   2020-11-02|            90|                 40|
|      1|   50|   2020-11-03|            90|                 90|
+-------+-----+-------------+--------------+-------------------+

正如你所看到的,排序窗口将改变帧从开始到当前行,所以求和将产生一个累积和而不是总和。然而,如果我们不使用排序,默认框架将是整个窗口,求和将产生总和。

5.写入表会使缓存失效

不完全是这样,但是如果您的缓存数据是基于某人刚刚向其追加了数据(或者覆盖了数据)的这个表,那么一旦您调用另一个操作,数据将被扫描并再次缓存。让我们看看这个例子:

df = spark.table(tableA)
df.cache()
df.count()  # now the data is placed in cache# someone writes to tableA now:
dx.write.mode('append').option('path', path).saveAsTable(tableA)# now df is no longer cached, but it will be again after calling some action on itdf.count()  # the data is now placed to memory again but its content was changed

因此,这里意想不到的事情是,如果有人同时追加表,对缓存的数据帧调用相同的计算可能会导致不同的结果。

6.为什么调用 show()会运行多个作业?

在 Spark 中,有两种类型的操作、转换和动作,前者是懒惰的,而后者将物化查询并运行作业。 show() 函数是一个动作,所以它运行一个作业,然而令人困惑的是有时它运行更多的作业。为什么会这样?一个典型的例子是这个查询:

(
  spark.table(table_name).filter(col('user_id') == xxx)
).show()

现在,根据数据的属性,情况可能如下图所示,这是 Spark UI 的一个屏幕截图,我们可以看到 Spark 在返回结果之前运行了五个作业:

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

还要注意,这些作业中的任务数量是不同的。第一个作业(作业 id = 10)只有一个任务!下一个作业运行了 4 个任务,然后是 20、100 个任务,最后是一个有 75 个任务的作业。顺便说一下,执行这个任务的集群有 32 个可用内核,因此 32 个任务可以并行运行。

在多个作业中执行查询的想法是为了避免处理所有输入数据。默认情况下, show 函数只返回 20 行(这可以通过传递 n 参数来改变),所以也许我们可以只处理数据的一个分区来返回 20 行。这就是为什么 Spark 首先运行一个只有一个任务的作业,只处理数据的一个分区,希望找到输出所需的 20 行。如果 Spark 没有找到这 20 行,它将启动另一个作业来处理另外四个分区(这就是为什么第二个作业有四个任务),这样情况会重复,Spark 在每个进一步的作业中都会增加要处理的分区数量,直到它找到所需的 20 行或所有分区都被处理。

这是一个有趣的优化,特别是当您的数据集非常大(包含许多分区)并且您的查询不是非常有选择性时,所以 Spark 实际上只能处理前几个分区来找到 20 行。另一方面,如果您的查询具有很强的选择性,例如您要在一个非常大的数据集中查找一行(甚至可能不存在),那么使用 collect 函数可能会更有用,该函数将从一开始就充分利用集群的潜力,并在一个作业中处理所有数据,因为最终无论如何都必须处理所有分区(如果记录不存在)。

7.如何确保用户定义的函数只执行一次?

众所周知,如果用户定义函数(UDF)不是必需的,最好避免使用,因为它们会带来一些性能损失(损失有多大,取决于 UDF 是用 scala/java 还是 python 实现的)。但不太明显的是,如果使用 UDF,它可能会比预期执行更多次,因此开销会变得更大。然而,这是可以避免的,因此总的惩罚将会减轻。让我们看一个简单的例子:

@udf('long')
def add_one(x):
    return x + 1(
    spark.range(10)
    .withColumn('increased', add_one(col('id')))
    .filter(col('increased') > 5)
).explain()

在本例中,我们创建了一个简单的 UDF,用于向 DataFrame 添加一个新列,然后基于该列进行筛选。如果我们通过调用 explain 来检查查询计划,我们将看到:

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

如您所见,BatchEvalPython 操作符在计划中出现了两次,这意味着 Spark 将执行 UDF 两次。显然,这不是最佳的执行计划,尤其是当 UDF 成为瓶颈时,这是常有的事。幸运的是,有一个很好的技巧可以让 Spark 只调用 UDF 一次,那就是让函数变得不确定(参见文档):

add_one = add_one.asNondeterministic()(
    spark.range(10)
    .withColumn('increased', add_one(col('id')))
    .filter(col('increased') > 5)
).explain()

现在,检查查询计划发现 UDF 只被调用了一次:

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

这是因为 Spark 现在认为函数是不确定的,所以调用它两次是不安全的,因为每次调用它都会返回不同的结果。同样值得理解的是,通过这样做,我们对优化器施加了一些约束,优化器现在将以与其他非确定性表达式类似的方式处理它,例如,过滤器将不会像我们在上面的 collect_list 函数中看到的那样被推送。

8.UDF 可以破坏你的数据分发

不是字面意思。但让我解释一下我这么说的意思。假设有这样一种情况,您希望连接两个分桶的表,并且还需要在其中一列上调用 UDF。分桶将允许您在不混乱的情况下进行连接,但是您需要以正确的顺序调用转换。考虑以下查询:

(
  dfA.join(dfB, 'user_id')
  .withColumn('increased', add_one('comments'))
).explain()

这里,我们将两个在 user_id 列上分桶的表连接到相同数量的桶,并在 dfA 的其中一列上应用 UDF ( add_one )。该计划如下所示:

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

在这里你可以看到一切都很好,因为计划没有交换操作符,执行将是无洗牌的,这正是我们所需要的,这是因为 Spark 知道数据的分布,可以用它来连接。

另一方面,让我们看看如果先应用 UDF,然后再执行连接会发生什么:

(
  dfA
  .withColumn('increased', add_one('comments'))
  .join(dfB, 'user_id')
).explain()

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

现在情况发生了变化,我们在计划中有两个交换操作符,这意味着 Spark 现在将在连接之前洗牌。这是因为调用 UDF 删除了关于数据分布的信息,Spark 现在不知道数据实际上分布得很好,它将不得不洗牌以确保分区是正确的。因此,调用 UDF 并没有真正破坏分布,但它删除了关于它的信息,所以 Spark 将不得不假设数据是随机分布的。

结论

在本文中,我们讨论了一些 Spark 特性的例子,这些特性可能不太明显,或者在编写查询时很容易忘记。其中一些使用不当会导致代码中的错误,例如,如果您忘记了对窗口排序将改变您的框架,或者如果一些输入参数为空,一些函数将产生空值(如 concat 函数)。在一些例子中,我们还看到了一些简单的优化技巧,比如使 UDF 不确定可以避免执行两次,或者如果表是分桶的(或者根据一些特定的分区进行分布),在连接后调用 UDF 可以避免混乱。

我们还看到,使用 SQL 函数有时比 DSL 函数更灵活,一个特别的例子是 array_sort ,它在 SQL 中允许您指定比较器函数来实现自定义排序逻辑。

使用 Python 的 SQLite 简介

原文:https://towardsdatascience.com/did-you-know-you-can-get-rid-of-excel-sheets-csv-files-when-working-with-python-3cf8164a57c4?source=collection_archive---------39-----------------------

电子表格的免费替代品

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

米利安·耶西耶在 Unsplash 上拍摄的照片

在信息报告领域,我们主要依靠 Excel 表格CSV 文件来满足我们的数据存储需求。随着 ExcelCSV 文件数量的增加,这些文件的管理成为一个真正的挑战。对于这样的场景,拥有数据库软件似乎是一个合适的解决方案。但是,由于成本和上线时间等因素,该解决方案被搁置一旁。

在本教程中,我们将向您介绍 SQLite3。 它是一个 Python 模块,创建了一个免费的、磁盘上的关系数据库管理系统。

1.)关于 SQLite DB 和 SQLite3 模块的简要说明

SQLite 是一个独立的无服务器 C 应用程序库,它提供了一个磁盘关系数据库管理系统(RDBMS) 。无服务器使得 SQLite 成为原型开发、移动应用和游戏开发的完美候选。

SQLite3 是一个 Python 模块,在标准 Python 安装中默认可用。它提供了一个使用 SQLite 数据库的接口,并确保数据库管理成为标准 Python 代码的一部分。

2.)如何创建数据库?

创建一个 SQLite 数据库就像执行一个 SQLite3 下面的代码块演示了同样的情况。

**import** sqlite3 **as** db
con = db.connect(**"Folder_Location/sample.db"**)

如果文件夹位置中已经存在具有所需名称的数据库,库将建立新的连接。否则,它创建一个新的数据库。

要查看该数据库、其相关表以及这些表中的数据,可以使用基于第三方 GUI 的应用程序SQLitebrowser**。**下面是应用程序界面截图:

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

屏幕截图— Sqlite 浏览器(图片由作者提供)

3.)PEP 249 和光标

在我们学习使用 SQLite3 执行 SQL 查询之前,让我们先来看看 PEP 249游标

PEP 249 是设计数据库接口的 Python 标准指南(像 SQLite3 模块)。它确保使用 Python 代码执行 SQL 查询的方法在数据库之间保持一致,并且支持可移植性。PEP 249 推荐使用游标来执行 SQL 查询。

光标是对象的,其方法定义为支持 SQL 查询执行。我们可以使用连接对象光标方法创建这个类的一个实例。下面的代码块演示了光标对象的创建。

### Previous Python Statements
**import** sqlite3 **as** db
con = db.connect(**"Folder_Location/sample.db"**)### Creating a cursor
cur = con.cursor()

4.)游标方法和 SQL 查询

创建游标对象后,我们可以使用它的方法来执行 SQL 查询。游标对象支持多种方法来执行 SQL 查询。其中一个清单如下:

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

光标方法(图片由作者提供)

对于本教程,我们将主要使用执行方法。关于 Sqlite3 模块的详细文档可以在 Python 官网 : 获得

表格创建

我们可以将 create table SQL 查询作为字符串传递给 execute 方法来创建新表。我们可以保持表格结构与我们想要存储在数据库中的 Excel 表CSV 文件相同。

#### Creating table using cursor
**cur.execute**('''CREATE TABLE IF NOT EXISTS **dept** 
(id text PRIMARY KEY, dept_name text NOT NULL,
dept_head text);''')

加载数据

为了将数据加载到数据库中,我们将使用 Pandas 库的内置函数。下面的代码块假设导入的 Excel 文件与 SQLite 数据库表中的列名相同。

#### Using Pandas builtin function to push the excel data
#### Importing pandas
import pandas as pd#### Importing CSV file as a dataframe
df = pd.read_csv("file location/name.csv")#### Pushing the dataframe in to the SQLite database
df.to_sql(table_name, con, if_exists = "append", index = False)

描述 : —我们使用了**‘to _ SQL’**data frame 方法的 4 个不同参数:

  • Table_name —我们正在推送数据的表
  • Con —连接到数据库时初始化的连接对象
  • If_exists —该参数检查 SQLite 数据库中是否存在表格。如果该表存在,数据框中的数据将追加到现有表中。另一个选项是用“代替作为参数实参。它将用数据框中的新数据替换历史数据。
  • 索引-这将确保数据框索引不会被添加到表中。

使用上面的函数,可以很容易地将 Excel / CSV 数据导入到他们的 SQLite 数据库中。如果情况允许插入手动记录,我们可以使用下面的 insert 语句:

#### Insert statement to insert a specific value in a database table
**cur.execute**('''INSERT INTO **dept**(id, dept_name,dept_head) VALUES (1, "Analytics", "Department Head");''')

在上面的语句中,我们将数据插入到部门表中。

  • 表名后提供的列列表是可选的
  • 在列列表后,使用关键字值,然后是值列表
  • 值列表中值的数量应该与列列表中列的数量相同
  • 值列表中的元素数应该与表中的列数相同

选择数据

一旦数据在 SQLite 表中可用,将它用于您的报告或分析目的是一个人将执行的关键活动。使用选择查询来完成工作**。**

#### Selecting data from the table
**for** rows **in** cur.execute(**'''select * from dept'''**):
    print(rows)

注意:在上面的例子中,光标对象被视为一个 iterable 对象,它循环以获取所有选中的记录。或者,可以使用获取一个、获取多个或获取所有方法来选择特定数量的记录。

删除记录

通常,在后期阶段,我们会发现表中的数据记录包含一些错误的值。使用标准的**删除查询,**我们可以删除特定的记录。

#### Deleting data in table
cur.execute('''**delete from dept where id in (3,4)**''')
con.commit()

注意:我们应该在每次插入、删除或更新操作之后执行一个提交方法**。**它确保数据更改成功地反映在数据库中。

结束语

在创建应用程序原型设计概念证明处理多个 CSV 文件时,我们大多数人都希望有一个服务器免费和轻量级版本的关系数据库。有了 SQLite 和它的 Python 接口的知识,我希望我们现在可以跨越这个鸿沟。

我希望这篇教程是有启发性的,并且你学到了一些新的东西。

我会在未来的教程中尝试引入更多有趣的话题。在此之前:

快乐学习!!!!

您知道吗——Python 技巧

原文:https://towardsdatascience.com/did-you-know-you-can-measure-the-execution-time-of-python-codes-14c3b422d438?source=collection_archive---------9-----------------------

测量 Python 代码的执行时间

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

UnsplashNeONBRAND 拍摄的照片

当我们开始精通一门编程语言时,我们渴望不仅交付最终目标,而且使我们的程序更高效。

你知道吗系列的这个教程中,我们将学习一些 Ipython 的魔法命令,它们可以帮助我们对 python 代码进行时间分析。

注意,出于本教程的目的,建议使用 Anaconda 发行版。

1.)分析一行代码

要检查一行 python 代码的执行时间,可以使用 %timeit 。下面是一个简单的例子来说明它是如何工作的:

#### Simple usage of magics command %timeit
**%timeit [num for num in range(20)]**#### Output
**1.08 µs ± 43 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)**

关键注意事项:

  • 在要评测的代码行之前使用 %timeit
  • 返回每次运行内循环r次数或运行和n次数计算的代码运行时间的平均值和标准差。在上面的例子中,列表理解被评估了 7 次,每次运行有一百万次循环(默认行为)。这平均花费了 1.08 微秒,标准偏差为 43 纳秒。
  • 你可以在调用魔术命令时自定义运行和循环的次数。下面的例子供参考:
#### Customizing number of runs and loops in %timeit magic command
**%timeit -r5 -n100 [num for num in range(20)]**1.01 µs ± 5.75 ns per loop (mean ± std. dev. of 5 runs, 100 loops each)

使用命令选项 **-r 和-n,**分别表示运行次数和循环次数,我们将时间曲线操作定制为 5 次运行和每次运行中的 100 次循环。

2.)剖析多行代码

本节向前迈进了一步,解释了如何分析一个完整的代码块。通过对**%下面有一个示例演示供参考:**

#### Time profiling the code block using %%timeit
**%%timeit -r5 -n1000
for i in range(10):
    n = i**2
    m = i**3
    o = abs(i)**#### Output
**10.5 µs ± 226 ns per loop (mean ± std. dev. of 5 runs, 1000 loops each)**

可以观察到循环的平均执行时间为 10.5 微秒。注意,相同的命令选项 -r 和-n 分别用于控制运行计数和每次运行中的循环数。

3.2)对代码块中的每一行代码进行时间剖析

到目前为止,在分析单行代码或完整代码块时,我们只查看了汇总统计数据。如果我们想评估代码块中每一行代码的性能,该怎么办?线路侧写师包来救援!!!

Line_profiler 包可用于对任何**功能进行逐行剖析。**要使用 line_profiler 软件包,请执行以下步骤:

  • 安装包line profiler包可以通过简单调用 pip 或 conda install 来安装。如果使用 Python 的 anaconda 发行版,建议使用 conda install
#### Installing line_profiler package
**conda install line_profiler**
  • 加载扩展 —安装后,您可以使用 IPython 加载 line_profiler IPython 扩展,该扩展作为该软件包的一部分提供:
#### Loading line_profiler's Ipython extension
**%load_ext line_profiler**
  • 对函数进行时间分析——一旦加载,使用以下语法对任何预定义的函数进行时间分析
%lprun -f **function_name_only** **function_call_with_arguments**

语法细节:

  • 对 line_profiler 的调用以关键字 %lprun 开始,后跟命令选项-f
  • 命令选项后面是函数名,然后是函数调用

在本练习中,我们将定义一个函数,接受身高(单位为米)和体重(单位为磅)列表作为输入,并将它们分别转换为厘米和千克。

**#### Defining the function**
def conversion(ht_mtrs, wt_lbs ):
    ht_cms = [ht*100 for ht in ht_mtrs]
    wt_kgs = [wt*.4535 for wt in wt_lbs]**#### Defining the height and weight lists:**
ht = [5,5,4,7,6]
wt = [108, 120, 110, 98]**#### Profiling the function using line_profiler** %lprun -f conversion conversion(ht,wt)---------------------------------------------------------------
**#### Output
Total time: 1.46e-05 s****File: <ipython-input-13-41e195af43a9>****Function: conversion at line 2****Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     2       1        105.0    105.0     71.9      ht_cms = [ht*100 for ht in ht_mtrs]
     3       1         41.0     41.0     28.1      wt_kgs = [wt*.4535 for wt in wt_lbs]**

输出详细信息:

  • 完整的时间曲线以 14.6 微秒为单位完成(参考输出的第一行)

生成的表格有 6 列:

  • 第 1 列(行号)-代码的行号(注意,第 1 行被故意从输出中省略,因为它只是函数定义语句)
  • 第 2 列(命中数)—线路被调用的次数
  • 第 3 列(时间)—花费在代码行上的时间单位数(每个时间单位为 14.6 微秒)
  • 第 4 列(每次命中)-第 3 列除以第 2 列
  • 第 5 列(%Time) —在花费的总时间中,在特定代码行上花费的时间百分比是多少
  • 第 6 列(内容)-代码行的内容

人们可以清楚地注意到,从米到厘米的高度转换几乎花费了总时间的 72%。

结束语

有了我们可以支配的每行代码的执行时间,我们就可以部署策略来提高代码的效率。在接下来的 3 个教程中,我们将分享一些最佳实践来帮助你提高代码的效率。

我希望这个【T42 你知道吗】系列的教程是有益的,你学到了一些新的东西。

会在以后的教程中尝试并带来更多有趣的话题。在此之前:

快乐学习!!!!

Pandas 中应用()和转换()的区别

原文:https://towardsdatascience.com/difference-between-apply-and-transform-in-pandas-242e5cf32705?source=collection_archive---------4-----------------------

一些最有用的熊猫把戏

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

照片由翻滚 926 上的退溅

Pandas 是一个令人惊叹的库,它包含大量用于操作数据的内置函数。在寻找应用自定义函数时,您可能会对以下两种选择感到困惑:

  • apply(*func, axis=0*):沿数据帧的一个轴调用函数*func*。它返回沿给定轴应用*func*的结果。
  • transform(*func, axis=0*):调用 self 上的函数*func*,产生一个带有变换值的数据帧。它返回一个与 自身 长度相同的 DataFrame。

他们采用相同的论点*func*axis。两者都沿着给定数据帧的轴调用*func*。那么区别在哪里呢?你如何选择一个而不是另一个?

在本文中,我们将介绍以下用法,并讨论它们的区别:

  1. 操纵值
  2. 结合groupby()结果

源代码请查看我的 Github repo

如果您不熟悉以下文章,请查看它们:

[## 何时使用 Pandas transform()函数

一些最有用的熊猫把戏

towardsdatascience.com](/when-to-use-pandas-transform-function-df8861aa0dcf) [## 熊猫应用简介,应用地图和地图

一个直观的熊猫教程,介绍如何使用 apply()和 applymap()应用一个函数,以及如何替换值…

towardsdatascience.com](/introduction-to-pandas-apply-applymap-and-map-5d3e044e93ff)

1.操纵值

apply()transform()都可以用来操作数值。让我们借助一些例子来看看它们是如何工作的。

df = pd.DataFrame({'A': [1,2,3], 'B': [10,20,30] })**def plus_10(x):
    return x+10**

对于整个数据帧

apply()transform()都可用于操作整个数据帧。

df.**apply(plus_10)**

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

df.**transform(plus_10)**

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

apply()transform()都支持 lambda 表达式,下面是 lambda 的等价表达式:

df.**apply(lambda x: x+10)**df.**transform(lambda x: x+10)**

为单列

apply()transform()都可以用于操作单个列

df['B_ap'] = df['B'].**apply(plus_10)**
# The lambda equivalent
df['B_ap'] = df['B'].**apply(lambda x: x+10)**df['B_tr'] = df['B'].**transform(plus_10)**
# The lambda equivalent
df['B_tr'] = df['B'].**transform(lambda x: x+10)**

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

有什么区别?

以下是三个主要区别

  • transform()(1)使用函数、字符串函数、函数列表和字典。但是,apply()只允许与函数一起使用。
  • transform()不能产生聚合结果。
  • (3) apply()一次处理多个系列。但是,transform()一次只允许处理单个系列。

让我们借助一些例子来看看它们。

(1) **transform()** 作用于函数,一个字符串函数,一个函数列表,一个字典。但是, **apply()** 只允许有一个功能。

对于transform(),我们可以将任何有效的熊猫字符串函数传递给func

df.transform(**'sqrt'**)

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

func可以是函数列表,例如 NumPy 中的sqrtexp:

df.transform(**[np.sqrt, np.exp]**)

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

func可以是轴标签的字典- >函数。例如

df.transform(**{
    'A': np.sqrt,
    'B': np.exp,
}**)

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

(2) **transform()** 不能产生聚合结果。

我们可以使用apply()来产生聚合结果,例如,总和

df.**apply(lambda x:x.sum())**A     6
B    60
dtype: int64

然而,当我们试图用transform()做同样的事情时,我们将得到一个值错误。我们遇到这个问题是因为transform()的输出必须是与 自身 长度相同的数据帧。

df.**transform(lambda x:x.sum())**

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

(3) **apply()** 一次处理多个系列的作品。但是, **transform()** 一次只允许处理单个系列。

为了演示这一点,让我们创建一个一次处理 2 个系列的函数。

def subtract_two(x):
    return x['B'] - x['A']

apply()subtract_twoaxis=1完美配合

df.apply(**subtract_two, axis=1**)0     9
1    18
2    27
dtype: int64

然而,当我们试图用transform()做同样的事情时,我们得到了一个值错误。这是因为transform() 一次只允许处理一个系列。

# Getting error when trying the same with transform
df.**transform(subtract_two, axis=1)**

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

当使用 lambda 表达式时,我们会得到相同的结果

# It is working
df.apply(**lambda x: x['B'] - x['A'], axis=1**)# Getting same error
df.transform(**lambda x: x['B'] - x['A'], axis=1**)

2.结合groupby()

apply()transform()都可以与groupby()配合使用。而且事实上,这是transform()最引人注目的用法之一。更多详情,请查看以下文章中的**“结合** **groupby()** 结果”:

[## 何时使用 Pandas transform()函数

一些最有用的熊猫把戏

towardsdatascience.com](/when-to-use-pandas-transform-function-df8861aa0dcf)

以下是与groupby()结合使用时的两个区别

  • (1) transform()返回与输入长度相同的数据帧
  • (2) apply()一次处理多个系列。但是,transform()一次只允许处理单个系列。

让我们创建一个数据框架,并通过一些示例来展示其区别

df = pd.DataFrame({
    'key': ['a','b','c'] * 4,
    'A': np.arange(12),
    'B': [1,2,3] * 4,
})

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

在上例中,可以通过 将数据分成三组。

(1) **transform()** 返回一个与输入长度相同的序列

为了演示这一点,让我们创建一个函数来产生一个聚合结果。

**# Aggregating the sum of the given Series
def group_sum(x):
    return x.sum()**

对于apply(),它为每组返回一个值,输出形状为(num_of_groups, 1)

gr_data_ap = df.groupby('key')['A']**.apply(group_sum)**
gr_data_ap**key
a     9
b    12
c    15**
Name: A, dtype: int64

对于transform(),它返回一个与给定数据帧长度相同的序列,输出形状为(len(df), 1)

gr_data_tr = df.groupby('key')['A']**.transform(group_sum)**
gr_data_tr**0     9
1    12
2    15
3     9
4    12
5    15
6     9
7    12
8    15**
Name: A, dtype: int64

(2) **apply()** 一次处理多个系列。但是 **transform()** 一次只允许处理单个系列。

这和我们在 1 中提到的区别是一样的。操作值,我们只是不需要在一个groupby()结果上指定参数axis

为了演示这一点,让我们创建一个一次处理 2 个系列的函数。

def subtract_two(x):
    return x['B'] - x['A']

apply()一次处理多个系列。

df.groupby('key')**.apply(subtract_two)****key   
a    0    1
     3   -2
     6   -5
b    1    1
     4   -2
     7   -5
c    2    1
     5   -2
     8   -5**
dtype: int64

然而,当我们用transform()尝试同样的方法时,我们得到了一个键错误

df.groupby('key')**.transform(subtract_two)**

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

摘要

最后,这里是一个总结

对于操作值,apply()transform()都可以用来操作整个数据帧或任何特定的列。但是有三个不同之处

  1. transform()可以接受一个函数、一个字符串函数、一个函数列表和一个字典。然而,apply()只允许一个函数。
  2. transform()无法产生汇总结果
  3. apply()一次处理多个系列。但是,transform()一次只允许处理单个系列。

用于与groupby()一起工作

  1. transform()返回与输入长度相同的序列
  2. apply()一次处理多个系列。但是,transform()一次只允许处理单个系列。

好了

感谢阅读。

请在我的 Github 上查看笔记本的源代码。

如果你对机器学习的实用方面感兴趣,请继续关注。

你可能会对我的其他一些熊猫文章感兴趣:

更多可以从我的 Github 中找到

概率和统计的区别

原文:https://towardsdatascience.com/difference-between-probability-and-statistics-d69db0ff3f71?source=collection_archive---------37-----------------------

让我们了解概率和统计是如何联系在一起的,哪一个更重要

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

照片由 Riho KrollUnsplash 上拍摄

正如著名数学家卡尔·皮尔逊所说:

“统计学是科学的语法。”

统计学是几乎所有技术进步的核心,在从生物统计学、金融、经济学到机器学习等广泛领域都有应用。

话虽如此,我们会发现没有任何一本统计学书籍不谈论概率的概念。事实上,它们是密切相关的数学分支,相互交织在一起。

在本文中,我们将了解两者的区别。然后,我们将以一个普遍存在的问题作为结束,这个问题是我们所有人都可能在某个时候面临的——从哪里开始?

概率:

概率是一个前瞻性的过程。它有助于预测下一个输出,因为我们知道数据生成模型的分布是一个随机过程

我们要计算 P(Y |θ);其中 Y 是结果,θ是定义随机过程的参数(可能是掷硬币、掷骰子等)。

有两个学派来解释概率:

**1)频数主义者:**主要是通过多次重复实验来计算某种结果出现的相对频率

让我们举一个公平掷硬币的例子。投掷硬币 10 次可能会显示 7 个“正面”,转换成 0.7 的概率,但是,如果我们重复这个实验足够长的时间,那么“正面”的频率计数就等于反面的频率计数,表明概率为 0.5。

**2)贝叶斯:**它主要讲的是

a)在看到新数据之前的信任程度,在术语“先验”中捕获

b)来自观测数据的更新信息,包含在由 L(θ|Y)表示的“可能性”中。

然后,通过取先验与似然性的乘积来更新后验概率:

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

**让我们了解更多关于可能性的知识:**由于现实生活中的过程是高度随机和复杂的,我们通常对它们的分布没有一个合理的概念,因此我们需要对θ进行更好的估计,以便能够更好地预测 P(Y/ θ)。

简而言之,我们需要估计最能描述生成给定数据的分布的θ。这就是统计进入图片的地方——计算可能性函数。

统计:

这是一个回顾过去的过程。它从潜在的随机过程中提取模式,以提出一个可能的量化模型来解释数据行为。

请注意,我们不一定知道定义潜在随机过程的所有特征。

统计模型从有限的可能值θ(称为模型空间θ)以及观察到的数据开始,以推断哪个θ会产生该数据。θ可以是定义概率分布的任何参数,如均值或方差。

统计建模也有助于我们量化推断分布或参数的误差。随着从相同的实验中收集更多的数据,估计的参数越来越接近真实的θ。也就是说,估计值周围的误差缩小了,我们对自己的估计更有信心了。这被称为置信区间。

现在,你会想这个错误是从哪里产生的,有多少数据足以推断出真正的参数。

这是因为我们只能研究数据样本,而不是整个人口,因为各种原因,比如数据的可用性,获取数据的成本等等。如果通过重复实验,我们可以使样本空间如此接近总体,我们就可以将样本参数估计的误差降低到 0,从而得到真实的参数。

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

概率与统计的区别——举例说明

例一:

**概率:**对于一个药物试验,整个人群以 1:10 的比例分为两组——A 组和 B 组,P(A) = 0.7,P(B) = 0.5,其中 P =药物作用成功的概率。从人群中随机抽取一名患者,成功治疗的概率是多少?

**统计:**现在,如果我们不知道真实人群的构成,并且观察从人群中随机选择的 1000 名患者中 700 名成功接受药物治疗的患者的数据。关于人口分布我们能推断出什么?

例 2:

**概率:**如果我们连续抛 3 次无偏的硬币,得到 HHT 的概率是多少?

**统计:**如果我们观察 3 次连续掷硬币的 HHT 结果,二项分布的参数 p 的估计值是多少?

概率和统计孰先孰后?

统计学致力于收集大量数据,以便能够产生洞察力并推动决策制定。统计学只包括但不限于概率模型的研究。

关于 StackOverflow 的讨论很多,但是,我想得出的结论是,理解概率的基础,足以理解统计学的中心法则,这是一个迭代的过程。

在这个过程中,当我们开始理解统计学和概率模型背后的概念时,我们不再受先学习什么的教育学的限制。

感谢阅读!!!

Python 中 type()和 isinstance()的区别

原文:https://towardsdatascience.com/difference-between-type-and-isinstance-in-python-47fae6fbb068?source=collection_archive---------22-----------------------

Python 初学者

Python 中的实例是什么?

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

克里斯汀娜·戈塔迪在 Unsplash 上的照片

上次我们谈到了 Python 中的 4 个内省工具,每个初学者都应该使用。如果你还没有读过这篇文章,可以看看下面这篇文章。最先引入的两个函数是type()isinstance()

[## Python 中 4 个易于使用的自省函数

如何在运行时检查对象

towardsdatascience.com](/4-easy-to-use-introspection-functions-in-python-49bd5ee3a2e8)

这里简单回顾一下。

Python 中的每个对象都有一个数据类型,一个内置的或者定制的。可以是整数int,字符串str,T21【NumPy】数组等…也可以是自定义类的对象。

给定某个对象objtype(obj)返回该对象的数据类型。如果对象是dtype的实例,则isintance(obj, dtype)返回True,否则返回False

那么 到底是某个类的一个实例 呢?

子类和继承

重要的事情先来。我们需要理解子类和继承。以下面的代码为例:

我们将类Rectangle(Shape)定义为Shape的子类,将Square(Rectangle)定义为Rectangle的子类。子类 继承其超类的 方法、属性和其他功能。

我们以一种方式定义层次结构,使得类Square的对象继承Rectangle的所有属性和方法。毕竟,正方形是矩形的特例,所有边的长度都相同。

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

Edvard Alexander lvaagUnsplash 上拍摄的照片

如果我们用length=5创建一个Square对象a,我们可以调用get_area()方法来计算它的面积。注意,这个方法没有在Square类中显式定义,而是在它的超类中指定,例如Rectangle

a = Square(5)
a.get_area() # 25

因此我们说Square(Rectangle) 从它的超类Rectangle继承了 方法get_area()

情况

现在你应该对子类和继承有了基本的了解。但是和type()isinstance()有什么关系呢?考虑以下关于方形物体a的陈述。这两个语句会返回什么?两个True?两个False

type(a) == Rectangle
isinstance(a, Rectangle)

首先,我们肯定地知道type(a)返回Square。并且类别Square不等于类别Rectangle。所以第一条语句返回False

Square == Rectangle # returns False

但是,第二条语句会返回True!因为SquareRectangle子类,所以对象aRectangle实例

子类的实例也是基类的实例。

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

梁杰森Unsplash 上的照片

外卖

综上所述,isinstance()考虑了继承,但是使用 equality ( ==)语句检查对象的type()却没有。

一个子类不等于它的基类。

感谢阅读!你可以注册我的时事通讯来接收我的新文章的更新。如果您对提高 Python 技能感兴趣,以下文章可能会有所帮助:

[## 我希望我能早点知道的 5 个 Python 特性

超越 lambda、map 和 filter 的 Python 技巧

towardsdatascience.com](/5-python-features-i-wish-i-had-known-earlier-bc16e4a13bf4) [## Python 3.8 中针对 Python 新手的 6 项新特性

请做好准备,因为 Python 2 不再受支持

towardsdatascience.com](/6-new-features-in-python-3-8-for-python-newbies-dc2e7b804acc)

机器学习和常规代码的区别

原文:https://towardsdatascience.com/differences-between-machine-learning-and-regular-code-9edd0e54ea58?source=collection_archive---------43-----------------------

机器会写和人类类似的代码吗?

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

Unsplash 上由凯·多尔纳拍摄的照片

机器学习(ML)和传统软件工程是生产软件的两种互补方法。学习过的算法和手工制作的算法有什么不同?理解差异有助于更好地发现这两种方法的机会。

下表总结了主要差异。本文的其余部分将更详细地讨论这些差异。

我所说的学习算法是指推荐引擎、欺诈检测器或其他从数据中学习的程序。我只关注实际的预测算法,并从定义中排除 API、参数拟合和其他支持代码。传统编程是指由软件工程师编写的应用程序。

建立有学问的和传统的程序

当然,在 CPU 指令的最基本层面上,学习算法和传统算法之间没有区别。一个习得的算法由循环、if-else 子句和乘法组成,就像人类程序员编写的程序一样。这种差异表现在程序是如何产生的。

在传统的软件开发方法中,软件工程师坐下来,一边喝着拿铁咖啡一边思考任务,将问题分成小块,最后写下解决每个子问题的代码。相反,学习算法是自动搜索的结果。优化方法的任务是找到最小化训练数据上的误差测量的算法。

这是否意味着机器学习和传统编程是开发类似应用程序的两种可选方式?不要!他们擅长不同的问题领域。ML 解决了复杂但狭窄的任务,在这些任务中,收集大量地面真实训练数据不是问题。图像和语音识别是突出的例子。另一方面,传统的软件工程更好地应用于广泛的问题,这些问题不能简单地简化为最大化的适应度函数。具有产品目录、购物车和结账功能的移动电子商务应用程序是传统方法的主要候选。

构造和应用领域的这些差异甚至在程序代码中也能体现出来。

代码的形状

学习算法的代码是严格结构化的。同时,它包括可以在学习期间调整的参数,以实现所需的行为。这类似于人类大脑如何进化来处理语言。儿童大脑的胚胎发育建立了神经元结构,赋予儿童学习任何语言的灵活性。这种灵活性是由于可调节的神经元连接强度和其他因素,当成长中的儿童学会用父母的语言交流时,这些因素会重新配置。

以类似的方式,学习算法的形状遵循预先制定的模板,通常是相对简单的数学运算块的统一链。例如,决策树是将数据分成组的 if-else 子句序列。下图显示了一个示例。同样,人工神经网络是组合输入特征的分层变换链。这种操作的链将输入(例如,图像)转换成期望的输出(例如,图像包含猫的概率)。学习算法通常由一种或几种不同类型的块组成。

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

银行是否应该接受贷款申请的决策树。树的结构是统一的,由以链的形式组织的 if-else 块组成。if 条件(变量和数字阈值)是可学习的参数。图片作者。

这些构建模块的形状是固定的(就像人脑中语言中心的结构),但它们包含可调参数(就像大脑中的神经连接强度):决策树中的变量和阈值以及神经网络中的连接权重。机器学习中的“学习”是指优化这些参数,使输出在训练数据上尽可能接近地匹配预期目标。

这种严格一致的结构是实现优化所必需的。我们只知道如何有效地优化某些类别的数学结构。因此,只有当程序由这种易于优化的操作块组成时,机器学习才能发现好的解决方案。幸运的是(有点令人惊讶的是),研究人员发现许多问题,尤其是图像和语言处理中的问题,确实符合这种所需的形状。

手工代码并不局限于遵循与 ML 代码相似的高度规则的形状。人类程序员可以编写任何需要的业务逻辑。开发人员倾向于分层组织源代码,每一层都有不同的功能。下图以一个游戏中的一些套路为例。主循环渲染一帧,处理声音效果,轮询游戏控制器等等。这些阶段中的每一个都调用它们自己的专用子程序。渲染包括计算对象可见性、纹理等等。与高度统一的学习程序相比,传统程序的功能更加多样化。

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

作为传统程序示例的游戏引擎架构。功能是像学习算法一样分层次组织的,但是这里每个分支和层都包含在别处找不到的专用子程序。图片作者。

人类开发人员的目标是编写他们的同伴可以理解的代码。这意味着,例如,将代码分割成小而连贯的功能,在大部分独立的模块之间定义清晰的边界,并尽量减少理解一段代码时需要记住的概念数量。一个好的传统源代码读起来就像是一个其他开发人员可以理解的故事。

然而,习得的算法不一定容易理解。没有人类可以理解的主函数或子程序作为研究的自然起点。相反,人们需要立刻理解抽象数学变换的整个错综复杂的链条。随着问题规模的增长,这项任务很快超出了人类的能力。因此,我们需要可解释的 AI 方法来解释由黑盒 ML 算法做出的决策。

外部依赖性

传统程序定期与外部世界交互并保持状态。它们调用库来执行操作和访问数据库中的数据。

学习到的算法通常是独立的,没有副作用。他们很少在预测之间保持任何状态(尽管随着对像神经图灵机这样的主题越来越感兴趣,这正在慢慢形成链条)。

展望未来

在未来,传统课程和学习课程之间的区别可能会开始变得模糊。通过优化方法的进步或仅仅因为原始计算能力的增长,机器学习可能会获得更大的灵活性。另一方面,可微分编程承诺让手工代码更易于优化和学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值