这两天在看这本 Data Science from Scrach ( PDF地址 ),是本不错的通俗易懂的数据科学入门书籍。其中一个章节介绍了一下 Python 的基础语法和数据科学常用的进阶语法,觉得介绍得不错,很简洁明了,所以将其翻译一下放在这里以作备忘。
数据科学中常用的 Python 语法(基础)
数据科学中常用的 Python 语法(进阶)
本章侧重于介绍在数据处理中非常有用的 Python 进阶语法和功能(基于 Python 2.7 )。
排序 Sorting
如果你想对 Python 的列表进行排序,可以使用列表的 sort 方法。如果你不想破坏原列表,可以使用 sorted 函数返回一个新的排好序的列表:
1 | x = [4,1,2,3] |
sort 或 sorted 是默认从小到大对列表进行排序的。
如果想让它从大到小排序,可以指定一个 reverse = True 的参数。
也可以自定义排序函数,让列表按照指定关键字进行排序:
1 | # 按照绝对值从大到小排序 |
列表解析 List Comprehensions
我们会经常遇到这样的情况,想要提取列表中特定几个元素组成新的列表,或是改变其中几个元素的值,或者皆有。Python 中的惯用做法就是 列表解析(List Comprehensions) :
1 | even_numbers = [x for x in range(5) if x % 2 == 0] # [0, 2, 4] |
类似地你可以将列表变成字典或集合:
1 | square_dict = { x : x * x for x in range(5) } # { 0:0, 1:1, 2:4, 3:9, 4:16 } |
如果你不需要使用到列表中的元素,那么可以将下划线当作变量:
1 | zeroes = [0 for _ in even_numbers] # 与列表 even_numbers 有相同的长度 |
列表解析支持多重 for 循环:
1 | pairs = [(x, y) |
后面的 for 循环可以使用前面 for 循环的结果:
1 | increasing_pairs = [(x, y) # 只包含 x < y 的数据对 |
未来我们将会经常用到列表解析。
生成器和迭代器 Generators and Iterators
列表有一个问题就是一不小心就会变得非常庞大,比如 range(1000000) 将会生成一个具有一百万个元素的列表。如果一次只处理一个数据,耗时可能会过长(或内存耗尽)。而实际上你可能只用到前几个数据,这样其他运算就是多余的。
而生成器可以让你只迭代那些需要用到的数据。可以使用函数和 yield 表达式来创建一个生成器:
1 | def lazy_range(n): |
译者补充:
生成器也是一种特殊迭代器,yield 是生成器实现迭代的关键。它作为生成器执行的暂停恢复点,可以对 yield 表达式进行赋值,也可以将 yield 表达式的值返回。任何包含 yield 语句的函数被称为生成器。跳出生成器时,生成器将当前执行状态保存,并在下次执行时恢复现场,以获得下一个迭代值。采用列表迭代将会占用大量地址空间,而使用生成器差不多只占用一个地址空间,从而达到节约内存的效果。
下面这个循环将一次消耗一个 yield 中的值直到消耗完毕:
1 | for i in lazy_range(10): |
(事实上 Python 自带了一个实现如上 _lazy_range_ 效果的函数,称为 xrange, Python 3 中称为 lazy.) 这意味着你可以创建一个无穷数列:
1 | def natural_numbers(): |
不过并不建议使用这种没有退出循环逻辑的语句。
- TIP
使用生成器迭代的一个缺点就是,从头到尾对元素只能迭代一次,如果想实现多次迭代,你只能每次都创建新的生成器或者使用列表。
第二种创建生成器的方法:利用括号内的解析表达式:
1 | lazy_evens_below_20 = (i for i in lazy_range(20) if i % 2 == 0) |
我们知道字典中的 items() 方法将返回一列表的字典中全部的键值对,但更多情况下,我们使用 iteritems() 生成器方法来进行迭代,每次只产生并返回一个键值对。
随机 Randomness
在学习数据科学的时候,我们将会经常需要生成随机数,所以只要导入 random 模块就能使用:
1 | import random |
如果你想获得可重现的结果,可以让 random 模块基于 random.seed 设置的内部状态生成伪随机(即确定性)数字:
1 | random.seed(10) # set the seed to 10 |
有时候我们也会使用 random.randrange 函数来生成一个指定范围内的随机数:
1 | random.randrange(10) # 从 range(10) = [0, 1, ..., 9] 中随机选择一个数 |
还有一些方法有时候用起来很方便,比如 random.shuffle 将打乱一个列表中的元素次序,重新生成一个随机排列的列表:
1 | up_to_ten = range(10) |
如果想从一个列表中随机选择一个元素,可以使用 random.choice 方法:
1 | my_best_friend = random.choice(["Alice", "Bob", "Charlie"]) # 我得到的是 "Bob" |
如果既想要生成一个随机序列,又不想打乱原列表,可以使用 random.sample 方法:
1 | lottery_numbers = range(60) |
你可以通过多次调用实现多个随机样本的选择(允许重复):
1 | four_with_replacement = [random.choice(range(10)) |
正则表达式 Regular Expressions
正则表达式用于文本搜索,略显复杂但非常有用,因而有大量的书专门讲解正则表达式。我们遇到它们的时候再进行具体的解释,下面是一些在 Python 中使用正则表达式的例子:
1 | import re |
面向对象编程 Object-Oriented Programming
与许多语言一样,Python 允许你定义封装数据的类和对其进行操作的函数。我们有时会使用它们来使我们的代码更清晰简洁。通过构建一个带有大量注释的示例来解释它们可能是最简单的。假设没有内置的 Python 集合,我们可能想要创建自己的 Set 类。那么这个类应当具备哪些功能呢?比如给定一个 Set ,我们需要能够向其中添加项目,从中删除项目,并检查它是否包含特定值。所以,我们将创建所有这些功能将其作为该类的成员函数。这样,我们就可以在 Set 对象之后用点访问这些成员函数:
1 | # 按照惯例,我们给出 _PascalCase_ 类的名称 |
然后我们就可以像这样使用 Set:
1 | s = Set([1,2,3]) |
函数工具 Functional Tools
部分函数 partial
当传递函数时,有时我们会想要使用某函数的部分功能以创建新函数。举个简单的例子,假设我们有两个变量的函数:
1 | def exp(base, power): |
我们想要利用它来创建一个函数,该函数输入一个变量,输出底数为 2 的幂函数 exp(2, power) 的结果。
当然,我们可以用 def 定义一个新的函数,虽然这看起来不太明智:
1 | def two_to_the(power): |
更聪明的做法是利用 functools.partial 方法:
1 | from functools import partial |
如果指定了名称,也可以使用 partial 方法填充其他的参数:
1 | square_of = partial(exp, power=2) |
如果你尝试在函数中间乱用参数,那么程序将很快就会变得混乱,所以请尽量避免这种行为。
映射 map
我们偶尔也会使用 map,reduce,和 filter 等函数来作为列表解析的功能替代:
1 | def double(x): |
map 方法还可以用于多参数函数到多列表的映射:
1 | def multiply(x, y): return x * y |
过滤器 filter
类似地,过滤器实现的是列表解析中 if 的功能:
1 | def is_even(x): |
缩减 reduce
reduce 方法不断合并列表中的第一个和第二个元素,然后将结果与第三个元素合并,并一直重复这个过程,直到得到一个唯一的结果:
1 | x_product = reduce(multiply, xs) # = 1 * 2 * 3 * 4 = 24 |
枚举 enumerate
偶尔会出现这样的情况,在遍历一个列表的时候同时要使用元素和其索引:
1 | # 不太 Python(不太简洁优美) |
最简洁的做法是使用 enumerate 枚举方法生成一个元组 tuples (index, element):
1 | for i, document in enumerate(documents): |
类似地,如果只想使用索引:
1 | for i in range(len(documents)): do_something(i) # 不简洁 |
后面我们将会经常使用这个方法。
压缩和参数解压 zip and Argument Unpacking
压缩 zip
我们经常会对两个或更多的列表进行压缩处理。压缩实际上就是将多列表转化为对应元组的单列表形式:
1 | list1 = ['a', 'b', 'c'] |
参数解压 Argument Unpacking
如果多个列表长度不一致,那么压缩过程会在最短列表尾部停止。你也可以使用一个奇怪的 “unzip” 解压缩技巧对列表进行解压:
1 | pairs = [('a', 1), ('b', 2), ('c', 3)] |
其中星号用于执行参数解压缩,它使用 pairs 的元素作为 zip 的单个参数。下面的调用方式具有同等效果:
1 | zip(('a', 1), ('b', 2), ('c', 3)) # 返回 [('a','b','c'), ('1','2','3')] |
参数解压也可以和其他函数共同使用:
1 | def add(a, b): return a + b |
虽然不太实用,不过是个不错的让代码变得简洁的技巧。
不定长参数传递 args and kwargs
假设我们要创建一个高阶函数,该函数输入一个旧函数,并返回一个新的函数,新函数是旧函数乘以 2 :
1 | def doubler(f): |
运行例子:
1 | def f1(x): |
然而只要传递的参数大于一个,该方法就不太好用了:
1 | def f2(x, y): |
所以我们需要指定一个函数,使得它能够容纳任意数量的参数,然后利用参数解压缩实现传递多个参数,这看起来有那么一点神奇:
1 | def magic(*args, **kwargs): |
当我们像这样定义一个函数时,args (arguments 的缩写)是一个包含未命名参数的元组,而 kwargs (keyword arguments 的缩写)是包含命名参数的字典。
它们也可以用在传递的参数为列表(或元组)或数组的情况:
n:
1 | def other_way_magic(x, y, z): |
你可以用它配合各种奇怪的方法使用,但我们只用它来解决高阶函数传递不定长参数的问题:
1 | def doubler_correct(f): |
欢迎来到数据科学的世界!
叮!恭喜你又打开了新世界的大门!接下来就可以去愉快地玩耍啦~