啃书:《利用Python进行数据分析》第三章

Python的数据结构、函数以及文件操作

在这个章节,我们将开始学习Python的内置功能,这些功能将会对本书后续内容做一个铺垫。虽然扩展库有很多,但是基础知识不能忘,在有了基础的前提下,我们对模块库的学习将会事半功倍。

这里首先从最基础的数据结构开始说明:元组、列表、字典和集合。然后会讨论到千变万化的函数。最后对Python对文件对象的操作和交互。

3.1数据结构和序列

元组

元组是个一个不可改变的固定长度对象。创建元组也非常简单,通过逗号即可完成:

In [1]: tup = 4, 5, 6

In [2]: tup
Out[2]: (4, 5, 6)

当用复杂的表达式定义元组的时候,尽量把值放在圆括号内,如下所示:

In [3]: nested_tup = (4, 5, 6), (7, 8)

In [4]: nested_tup
Out[4]: ((4, 5, 6), (7, 8))

tuple可以将任意序列或迭代器转换成元组:

In [5]: tuple([4, 0, 2])
Out[5]: (4, 0, 2)

In [6]: tup = tuple('string')

In [7]: tup
Out[7]: ('s', 't', 'r', 'i', 'n', 'g')

可以用方括号访问元组中的元素。和C、C++、JAVA等语言一样,序列是从0开始的:

In [8]: tup[0]
Out[8]: 's'

元组中存储的对象可能是可变对象。一旦创建了元组,元组中的对象就不能修改了:

In [9]: tup = tuple(['foo', [1, 2], True])

In [10]: tup[2] = False
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-c7308343b841> in <module>()
----> 1 tup[2] = False
TypeError: 'tuple' object does not support item assignment

如果元组中的某个对象是可变的,比如列表,可以在原位进行修改:

In [11]: tup[1].append(3)

In [12]: tup
Out[12]: ('foo', [1, 2, 3], True)

可以用加号运算符将元组串联起来:

In [13]: (4, None, 'foo') + (6, 0) + ('bar',)
Out[13]: (4, None, 'foo', 6, 0, 'bar')

元组乘以一个整数,像列表一样,会将几个元组的复制串联起来:

In [14]: ('foo', 'bar') * 4
Out[14]: ('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')

python中并没有进行复制,而时引用。

拆分数组

如果你想将元组赋值给类似元组的变量,Python会试图拆分等号右边的值:

In [15]: tup = (4, 5, 6)

In [16]: a, b, c = tup

In [17]: b
Out[17]: 5

即使含有元组的元组也会被拆分:

In [18]: tup = 4, 5, (6, 7)

In [19]: a, b, (c, d) = tup

In [20]: d
Out[20]: 7

使用这个功能,你可以很容易地替换变量的名字,其它语言可能是这样:

tmp = a
a = b
b = tmp

但是在Python中,替换可以这样做:

In [21]: a, b = 1, 2

In [22]: a
Out[22]: 1

In [23]: b
Out[23]: 2

In [24]: b, a = a, b

In [25]: a
Out[25]: 2

In [26]: b
Out[26]: 1

变量拆分常用来迭代元组或列表序列:

In [27]: seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]

In [28]: for a, b, c in seq:
   ....:     print('a={0}, b={1}, c={2}'.format(a, b, c))
a=1, b=2, c=3
a=4, b=5, c=6
a=7, b=8, c=9

tuple方法

因为元组的大小以及其中内容不能修改,他的方法都很简单,其中有一个就是count方法,它可以统计某个指定值的出现次数:

In [34]: a = (1, 2, 2, 2, 3, 4, 2)

In [35]: a.count(2)
Out[35]: 4

列表

和元组相比,列表就显得多样化很多,它的内容长度均可变。你可以通过方括号定义,或者使用list函数进行设置:

In [36]: a_list = [2, 3, 7, None]

In [37]: tup = ('foo', 'bar', 'baz')

In [38]: b_list = list(tup)

In [39]: b_list
Out[39]: ['foo', 'bar', 'baz']

In [40]: b_list[1] = 'peekaboo'

In [41]: b_list
Out[41]: ['foo', 'peekaboo', 'baz']

添加和删除

当然列表也支持了添加和删除的函数。可以用append在列表末尾添加元素:

In [45]: b_list.append('dwarf')

In [46]: b_list
Out[46]: ['foo', 'peekaboo', 'baz', 'dwarf']

可以用insert在指定位置插入元素:

In [47]: b_list.insert(1, 'red')

In [48]: b_list
Out[48]: ['foo', 'red', 'peekaboo', 'baz', 'dwarf']

可以用pop函数进行删除,它的作用是删除并返回指定位置元素:

In [49]: b_list.pop(2)
Out[49]: 'peekaboo'

In [50]: b_list
Out[50]: ['foo', 'red', 'baz', 'dwarf']

可以用remove去除指定值,remove会先寻找第一个值并删除:

In [51]: b_list.append('foo')

In [52]: b_list
Out[52]: ['foo', 'red', 'baz', 'dwarf', 'foo']

In [53]: b_list.remove('foo')

In [54]: b_list
Out[54]: ['red', 'baz', 'dwarf', 'foo']

切片

python中用切片可以选取序列类型中的一部分,切片基本形式是在方括号中使用start:stop

In [73]: seq = [7, 2, 3, 7, 5, 6, 0, 1]

In [74]: seq[1:5]
Out[74]: [2, 3, 7, 5]

切片也可以被序列赋值:

In [75]: seq[3:4] = [6, 3]

In [76]: seq
Out[76]: [7, 2, 3, 6, 3, 5, 6, 0, 1]

切片的起始元素是包括的,不包含结束元素。因此,结果中包含的元素个数是stop - start

startstop都可以被省略,省略之后,分别默认序列的开头和结尾:

In [77]: seq[:5]
Out[77]: [7, 2, 3, 6, 3]

In [78]: seq[3:]
Out[78]: [6, 3, 5, 6, 0, 1]

负数表明从后向前切片:

In [79]: seq[-4:]
Out[79]: [5, 6, 0, 1]

In [80]: seq[-6:-2]
Out[80]: [6, 3, 5, 6]

序列函数

enumerate函数

迭代一个序列时,如果想要跟踪到当前遍历项的序号。手动方法可能如下:

i = 0
for value in collection:
   # do something with value
   i += 1

因为这么做很常见,Python内建了一个enumerate函数,可以返回(i, value)元组序列:

for i, value in enumerate(collection):
   # do something with value

当你索引数据时,使用enumerate的一个好方法是计算序列(唯一的)dict映射到位置的值:

In [83]: some_list = ['foo', 'bar', 'baz']

In [84]: mapping = {}

In [85]: for i, v in enumerate(some_list):
   ....:     mapping[v] = i

In [86]: mapping
Out[86]: {'bar': 1, 'baz': 2, 'foo': 0}

zip函数

zip可以将多个列表、元组或其它序列成对组合成一个元组列表:

In [89]: seq1 = ['foo', 'bar', 'baz']

In [90]: seq2 = ['one', 'two', 'three']

In [91]: zipped = zip(seq1, seq2)

In [92]: list(zipped)
Out[92]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

zip可以处理任意多的序列,元素的个数取决于最短的序列:

In [93]: seq3 = [False, True]

In [94]: list(zip(seq1, seq2, seq3))
Out[94]: [('foo', 'one', False), ('bar', 'two', True)]

zip的常见用法之一是同时迭代多个序列,可能结合enumerate使用:

In [95]: for i, (a, b) in enumerate(zip(seq1, seq2)):
   ....:     print('{0}: {1}, {2}'.format(i, a, b))
   ....:
0: foo, one
1: bar, two
2: baz, three

字典

字典可能是Python最为重要的数据结构。它更为常见的名字是哈希映射或关联数组。它是键值对的大小可变集合,键和值都是Python对象。创建字典的方法之一是使用尖括号,用冒号分隔键和值:

In [101]: empty_dict = {}

In [102]: d1 = {'a' : 'some value', 'b' : [1, 2, 3, 4]}

In [103]: d1
Out[103]: {'a': 'some value', 'b': [1, 2, 3, 4]}

你可以像访问列表或元组中的元素一样,访问、插入或设定字典中的元素:

In [104]: d1[7] = 'an integer'

In [105]: d1
Out[105]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}

In [106]: d1['b']
Out[106]: [1, 2, 3, 4]

你可以用检查列表和元组是否包含某个值的方法,检查字典中是否包含某个键:

In [107]: 'b' in d1
Out[107]: True

可以用del关键字或pop方法(返回值的同时删除键)删除值:

In [108]: d1[5] = 'some value'

In [109]: d1
Out[109]: 
{'a': 'some value',
 'b': [1, 2, 3, 4],
 7: 'an integer',
 5: 'some value'}

In [110]: d1['dummy'] = 'another value'

In [111]: d1
Out[111]: 
{'a': 'some value',
 'b': [1, 2, 3, 4],
 7: 'an integer',
 5: 'some value',
 'dummy': 'another value'}

In [112]: del d1[5]

In [113]: d1
Out[113]: 
{'a': 'some value',
 'b': [1, 2, 3, 4],
 7: 'an integer',
 'dummy': 'another value'}

In [114]: ret = d1.pop('dummy')

In [115]: ret
Out[115]: 'another value'

In [116]: d1
Out[116]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}

keysvalues是字典的键和值的迭代器方法。虽然键值对没有顺序,这两个方法可以用相同的顺序输出键和值:

In [117]: list(d1.keys())
Out[117]: ['a', 'b', 7]

In [118]: list(d1.values())
Out[118]: ['some value', [1, 2, 3, 4], 'an integer']

update方法可以将一个字典与另一个融合:

In [119]: d1.update({'b' : 'foo', 'c' : 12})

In [120]: d1
Out[120]: {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}

用序列创建字典

常常,你可能想将两个序列配对组合成字典。下面是一种写法:

mapping = {}
for key, value in zip(key_list, value_list):
    mapping[key] = value

因为字典本质上是2元元组的集合,dict可以接受2元元组的列表:

In [121]: mapping = dict(zip(range(5), reversed(range(5))))

In [122]: mapping
Out[122]: {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

集合

集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以用两种方式创建集合:通过set函数或使用尖括号set语句:

In [133]: set([2, 2, 2, 1, 3, 3])
Out[133]: {1, 2, 3}

In [134]: {2, 2, 2, 1, 3, 3}
Out[134]: {1, 2, 3}

集合支持合并、交集、差分和对称差等数学集合运算。考虑两个示例集合:

In [135]: a = {1, 2, 3, 4, 5}

In [136]: b = {3, 4, 5, 6, 7, 8}

合并是取两个集合中不重复的元素。可以用union方法,或者|运算符:

In [137]: a.union(b)
Out[137]: {1, 2, 3, 4, 5, 6, 7, 8}

In [138]: a | b
Out[138]: {1, 2, 3, 4, 5, 6, 7, 8}

交集的元素包含在两个集合中。可以用intersection&运算符:

In [139]: a.intersection(b)
Out[139]: {3, 4, 5}

In [140]: a & b
Out[140]: {3, 4, 5}

所有逻辑集合操作都有另外的原地实现方法,可以直接用结果替代集合的内容。对于大的集合,这么做效率更高:

In [141]: c = a.copy()

In [142]: c |= b

In [143]: c
Out[143]: {1, 2, 3, 4, 5, 6, 7, 8}

In [144]: d = a.copy()

In [145]: d &= b

In [146]: d
Out[146]: {3, 4, 5}

3.2函数

函数是Python中最主要也是最重要的代码组织和复用手段。作为最重要的原则,如果你要重复使用相同或非常类似的代码,就需要写一个函数。通过给函数起一个名字,还可以提高代码的可读性。
函数使用def关键字声明,用return关键字返回值:

def my_function(x, y, z=1.5):
    if z > 1:
        return z * (x + y)
    else:
        return z / (x + y)

同时拥有多条return语句也是可以的。如果到达函数末尾时没有遇到任何一条return语句,则返回None。

函数可以有一些位置参数(positional)和一些关键字参数(keyword)。关键字参数通常用于指定默认值或可选参数。在上面的函数中,x和y是位置参数,而z则是关键字参数。也就是说,该函数可以下面这两种方式进行调用:

my_function(5, 6, z=0.7)
my_function(3.14, 7, 3.5)
my_function(10, 20)

匿名(lambda)函数

Python支持一种被称为匿名的、或lambda函数。它仅由单条语句组成,该语句的结果就是返回值。它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。

def short_function(x):
    return x * 2

equiv_anon = lambda x: x * 2

本书其余部分一般将其称为lambda函数。它们在数据分析工作中非常方便,因为你会发现很多数据转换函数都以函数作为参数的。直接传入lambda函数比编写完整函数声明要少输入很多字(也更清晰),甚至比将lambda函数赋值给一个变量还要少输入很多字。看看下面这个简单得有些傻的例子:

def apply_to_list(some_list, f):
    return [f(x) for x in some_list]

ints = [4, 0, 1, 5, 6]
apply_to_list(ints, lambda x: x * 2)

虽然你可以直接编写[x *2for x in ints],但是这里我们可以非常轻松地传入一个自定义运算给apply_to_list函数。

python的函数库非常庞大,有兴趣的同学可以对照官方文档进行详细学习。

3.3文件处理

本书的代码示例大多使用诸如pandas.read_csv之类的高级工具将磁盘上的数据文件读入Python数据结构。但我们还是需要了解一些有关Python文件处理方面的基础知识。好在它本来就很简单,这也是Python在文本和文件处理方面的如此流行的原因之一。

为了打开一个文件以便读写,可以使用内置的open函数以及一个相对或绝对的文件路径:

In [207]: path = 'examples/segismundo.txt'

In [208]: f = open(path)

默认情况下,文件是以只读模式(‘r’)打开的。然后,我们就可以像处理列表那样来处理这个文件句柄f了,比如对行进行迭代:

for line in f:
    pass

从文件中取出的行都带有完整的行结束符(EOL),因此你常常会看到下面这样的代码(得到一组没有EOL的行):

In [209]: lines = [x.rstrip() for x in open(path)]

In [210]: lines
Out[210]: 
['Sueña el rico en su riqueza,',
 'que más cuidados le ofrece;',
 '',
 'sueña el pobre que padece',
 'su miseria y su pobreza;',
 '',
 'sueña el que a medrar empieza,',
 'sueña el que afana y pretende,',
 'sueña el que agravia y ofende,',
 '',
 'y en el mundo, en conclusión,',
 'todos sueñan lo que son,',
 'aunque ninguno lo entiende.',
 '']

如果使用open创建文件对象,一定要用close关闭它。关闭文件可以返回操作系统资源:

In [211]: f.close()

用with语句可以可以更容易地清理打开的文件:

In [212]: with open(path) as f:
   .....:     lines = [x.rstrip() for x in f]

如果输入f =open(path,‘w’),就会有一个新文件被创建在examples/segismundo.txt,并覆盖掉该位置原来的任何数据。另外有一个x文件模式,它可以创建可写的文件,但是如果文件路径存在,就无法创建。表3-3列出了所有的读/写模式。

如图3-1 列出了一些常用的文件方法

啃书:《利用Python进行数据分析》第三章_第1张图片

图3-1 Python重要的文件方法或属性

尽快我们很少使用python原生的方法进行数据读取和处理,但是打好基础才是我们后续学习的充分必要条件。

我们已经学过了Python的基础、环境和语法,接下来学习NumPy和Python的面向数组计算。

你可能感兴趣的:(python,数据分析,开发语言)