数据科家应该了解的Python特性

177 阅读7分钟

从数据整理到机器学习,Python 已成为数据科学事实上的语言。但是你是否利用了 Python 提供的所有功能?

在本文中,我们将深入探讨数据科学应该了解的 12 个 Python 功能。

Comprehensions

列表推导式如下:

_list = [x**2 for x in range(1, 11)]
print(_list)
# output
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

有时我们需要将一个多层嵌套的列表展平成一维列表,比如:

nested_list = [[1, 2], [3, 4], [5, 6]]

要将其展平,可以使用嵌套的列表推导式:

flattened = [x 
             for sublist in nested_list 
             for x in sublist]

这里的嵌套列表推导式依次迭代每个子列表,并将其中的每个元素提取出来,放入一个新的展平列表中。

新的flattened列表为: [1, 2, 3, 4, 5, 6]

这样可以避免使用多层for循环来展平列表,语法更简洁。

嵌套列表推导式是一个展平复杂列表的高效方法,数据科学家在处理嵌套数据时可以活用它。

除了列表推导式还有字典推导式,集合推导式,生成器推导式

下面是一个典型的例子:

values = [1, 2, 3, 4]

dict_comp = {x: x**2 for x in values} 
print(dict_comp)
# {1: 1, 2: 4, 3: 9, 4: 16}

set_comp = {x for x in values if x % 2 == 0}
print(set_comp)
# {2, 4}

gen_comp = (x**2 for x in values)
print(list(g for g in gen_comp))
# [1, 4, 9, 16]
  • dict_comp创建了一个字典,{1: 1, 2: 4, 3: 9, 4: 16}
  • set_comp创建了一个集合,{2, 4}
  • gen_comp创建了一个生成器对象,可以逐个生成1, 4, 9, 16

掌握这三种推导式的用法,可以帮助我们对数据进行转换和选择,写出简洁高效的代码。

Enumerate

enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

这在处理数据集时非常有用,因为它允许轻松访问和操作单个元素,同时跟踪其索引位置。

for idx, value in enumerate(["a", "b", "c", "d"]):
    print("idx = {}, value = {}".format(idx, value))
    
# output:
# idx = 0, value = a 
# idx = 1, value = b 
# idx = 2, value = c 
# idx = 3, value = d

enumerate()会将序列元素和索引组合成一个元组,这样既可以访问元素值也可以获取其在序列中的位置。

掌握enumerate()的用法,可以帮助我们在迭代序列的同时关注每个元素的索引信息。

zip

zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

x = [1, 2, 3, 4]
y = [5, 6, 7, 8]
z = zip(x, y)
l = list(z)
print(l)

# output
# [(1, 5), (2, 6), (3, 7), (4, 8)]

使用zip(*)解压打包的元组列表

a,b = zip(*l)
print(a)

# output
# (1, 2, 3, 4)

zip()会将x和y对应位置的元素"压缩"成一个元组,这样可以同时迭代访问多个序列,很方便。

掌握zip()的用法,我们可以同时处理多个可迭代对象,实现并行迭代。这在数据处理和分析中很常见。

Generators

Python 中的生成器函数的定义与普通函数类似,但每当它需要生成一个值时,它都会使用 Yield 关键字而不是 return 来生成。如果 def 的主体包含yield,则该函数将自动成为 Python 生成器函数。

Python 中的生成器是一种可迭代类型,它允许动态生成一系列值,而不是一次生成所有值并将它们存储在内存中。

这使得它们对于处理无法放入内存的大型数据集非常有用,因为数据是小块或批量处理的,而不是一次性处理的。

创建一个简单的生成器并且用for循环打印

def simple_generator_fun():
	yield 1			
	yield 2			
	yield 3			

for value in simple_generator_fun():
	print(value)
# output
# 1
# 2
# 3

Python 生成器函数返回一个可迭代的生成器对象

生成器对象可以通过调用生成器对象的 next 方法或在“for in”循环中使用生成器对象来使用。

# A simple generator for Fibonacci Numbers
def fib(limit):
	a, b = 0, 1

	while a < limit:
		yield a
		a, b = b, a + b

x = fib(5)

print(next(x))
print(next(x))
print(next(x))
print(next(x))
print(next(x))

print("\nUsing for in loop")
for i in fib(5):
	print(i)

这样逐个yield生成数列的值,而不是把完整的巨大数列存入内存。这展示了生成器的关键特性——延迟计算,以及如何通过生成器来节省内存和有效处理大量数据。如果用return而不是yield,内存消耗会很大。

总之,生成器是Python中非常有用的工具,可以用来流式处理数据,延迟计算,节省内存空间等。掌握它的使用可以提高我们程序的性能。

Lambda functions

lambda是Python中用于创建匿名函数的关键字,匿名函数即没有具体名字的单行函数。

lambda函数非常适合用于即时定义一些自定义函数,如数据预处理、特征工程等场景。

下面这个例子使用lambda来过滤一个数字列表,留下偶数:

numbers = [1, 2, 3, 4, 5, 6]

even_numbers = filter(lambda x: x % 2 == 0, numbers)

print(list(even_numbers))

# output
# [2, 4, 6]

这里我们传入了一个lambda函数作为filter()的第一个参数。lambda函数判断一个数除以2的余数是否为0,来判断一个数是否为偶数。

filter()会遍历numbers列表,过滤掉不符合lambda函数条件的元素。

这样即时定义了一个匿名过滤函数,避免了定义额外的命名函数,使代码更简洁。

lambda的这种即时函数的特性,非常适合需要对数据进行预处理、转换,或模型结果进行评估的场景。掌握lambda可以让我们的代码更简洁高效

Map, filter, reduce

函数map、filter和reduce是三个用于操作和转换数据的内置函数。

  • map函数:对可迭代对象(如列表)的每个元素应用函数,生成新的列表
  • filter函数:根据条件筛选可迭代对象中的元素,生成新的列表
  • reduce函数:对可迭代对象中的元素顺序迭代调用函数,生成单一返回值

下面这个例子组合使用了三个函数,计算一个列表中偶数的平方和:

squared_evens = reduce(lambda x, y: x + y,
                       map(lambda x: x**2, 
                           filter(lambda x: x % 2 == 0, numbers)))

print(squared_evens)
# output
# 120

这里filter函数过滤出偶数,map函数计算平方,reduce函数进行求和。把多个操作组合起来,代码更加简洁。

掌握这三个函数的组合拳,可以大大简化数据处理流程,写出更Pythonic的代码。

Any and All

两个内置函数用来检查可迭代对象中是否存在或全部满足某个条件的元素。

  • any() 判断可迭代对象中是否 存在 满足条件的元素,返回bool值。

  • all() 判断可迭代对象中是否 全部 满足条件的元素,返回bool值。

这两个函数可用来检查数据集或子集是否满足某条件,非常实用。例如检查是否存在空值,或者值是否在某范围内等。

下面是一个简单的示例,检查是否存在偶数且全部都是奇数:

numbers = [1, 3, 5, 7]

print(any(n % 2 == 0 for n in numbers)) # False
print(all(n % 2 != 0 for n in numbers)) # True

any()用来判断是否存在偶数,all()用来判断是否全部都是奇数。

掌握any()和all()的区别和用法,可以帮助我们简洁地表达数据条件判断,并对数据集进行过滤筛选。

next

next()函数用于从一个迭代器中获取下一个元素,迭代器对象(如列表、元组等)都可以通过next()逐个取出元素。

next()函数在数据科学中处理迭代器和生成器对象很常用,它可以让我们逐个获取可迭代对象中的元素,这对处理大量数据或流式数据很有用。

下面这个例子中,我们定义了一个生成0到1之间随机数的生成器函数random_numbers(),然后使用next()函数取出其中第一个大于0.9的数:

import random

def random_numbers():
  while True:
    yield random.random()


first_above_09 = next(x for x in random_numbers() if x > 0.9)

next()允许我们按需获取生成器的下一个元素,而不是把所有元素都计算出来存入内存。

掌握next()的用法,我们可以逐个处理大量数据,节省内存空间,提高代码效率。

defaultdict

defaultdict是内置dict的子类,它可以为不存在的键提供默认值,非常适合处理缺失或不完整的数据。

比如在处理稀疏矩阵或特征向量时,可以利用defaultdict处理缺失情况。也可以用它来统计分类变量的频数。

下面是一个统计列表中元素个数的例子。使用int作为默认工厂函数,不存在的键会初始化为0:

from collections import defaultdict

items = ['a', 'b', 'c', 'a', 'b', 'a']

d = defaultdict(int)

for item in items:
    d[item] += 1
    
print(d)
# output
# defaultdict(<class 'int'>, {'a': 3, 'b': 2, 'c': 1})

通过defaultdict我们可以方便地处理字典中的缺失键,避免了检查键存在与否的麻烦。

掌握defaultdict的用法,可以帮助我们更好地处理不完整的数据,写出更简洁的代码。

partial

partial来自functools模块,它可以用来从一个现有函数中创建一个新的函数,并固定住原函数的部分参数。

partial的用途之一是创建定制的函数或数据转换,通过提前填充部分参数来减少重复代码。

下面这个例子使用partial基于add函数创建了一个新的increment函数,固定掉了一个参数为1:

from functools import partial

def add(x, y):
    return x + y

increment = partial(add, 1)

print(increment(1)) # 2

调用increment(1)实际上相当于调用add(1, 1)。

使用partial可以避免手动重复传入固定参数的麻烦。掌握这一技巧可以让我们创建更加灵活方便的函数。

lru_cache

lru_cache来自functools模块,它是一个缓存函数结果的装饰器,可以使用有限大小的缓存提升函数执行效率。

lru_cache非常适合优化计算开销大而且参数组合重复的函数或模型训练流程。

缓存结果可以减少函数重复计算,大幅提升执行速度,降低计算成本。

下面是一个使用缓存来有效计算斐波那契数的例子(这在计算机科学中称为记忆化):

from functools import lru_cache

@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)
    
print(fib(40))

lru_cache通过缓存来避免重复递归计算。

掌握lru_cache的使用,可以大大优化开销大的函数和程序流程,缓存重复计算的结果。

Dataclasses

@dataclass是一个装饰器,可以根据类中定义的属性自动为一个类生成 __init____repr____eq__等特殊方法。

这可以减少定义类时需要编写的样板代码量。dataclass对象可以表示数据点、特征向量、模型参数等。

下面这个例子使用@dataclass来定义一个简单的Person类,包含name、age和city三个属性:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int 
    city: str
    
p = Person('John', 20, 'New York')
print(p)
# output
# Person(name='John', age=20, city='New York')

@dataclass自动生成了__repr__方法。

掌握@dataclass的用法,可以帮助我们更简单地定义包含数据的类,减少重复代码的编写。

以上就是本文的全部内容。如果还有其他特性欢迎在评论区留言。