Python知识点大全

Python基础

变量的命名和使用

  • 变量名只能包含字母、数字和下划线。变量名可以字母或下划线打头,但不能以数字打头,例如,可将变量命名为message_1,但不能将其命名为1_message

  • 变量名不能包含空格,但可使用下划线来分隔其中的单词。例如,变量名greeting_message可行,但变量名greeting message会引发错误。

  • 不要将Python关键字和函数名用作变量名,即不要使用Python保留用于特殊用途的单词,如print

  • 变量名应既简短又具有描述性。例如,namen好,student_names_n好,name_lengthlength_of_persons_name好。

  • 慎用小写字母l和大写字母O,因为它们可能被人错看成数字1和0。

注释

单行注释

Python 中单行注释以 # 开头

例如:

1
2
# 这是一个注释 
print("Hello, World!")

多行注释

多行注释用三个单引号 ‘’’ 或者三个双引号 “”” 将注释括起来

例如:

单引号(’’’)

1
2
3
4
5
6
'''
这是多行注释,用三个单引号
这是多行注释,用三个单引号 这是多行注释,
用三个单引号
'''
print("Hello, World!")

双引号(”””)

1
2
3
4
5
6
"""
这是多行注释,用三个双引号
这是多行注释,用三个双引号
这是多行注释,用三个双引号
"""
print("Hello, World!")

进制转换

十进制转N进制

1
2
3
4
5
6
7
# 获取用户输入十进制数 
dec = int(input("输入数字:"))

print("十进制数为:", dec)
print("转换为二进制为:", bin(dec))
print("转换为八进制为:", oct(dec))
print("转换为十六进制为:", hex(dec))

二进制转N进制

1
2
3
4
5
6
7
8
9
binary_number = '101010'
decimal_number = int(binary_number, 2) # 二进制转换为十进制
octal_number = oct(decimal_number) # 十进制转换为八进制
hexadecimal_number = hex(decimal_number) # 十进制转换为十六进制

print('二进制数:', binary_number)
print('转换为十进制:', decimal_number)
print('转换为八进制:', octal_number)
print('转换为十六进制:', hexadecimal_number)

八进制转N进制

1
2
3
4
5
6
7
8
9
octal_number = '52'
decimal_number = int(octal_number, 8) # 八进制转换为十进制
binary_number = bin(decimal_number) # 十进制转换为二进制
hexadecimal_number = hex(decimal_number) # 十进制转换为十六进制

print('八进制数:', octal_number)
print('转换为十进制:', decimal_number)
print('转换为二进制:', binary_number)
print('转换为十六进制:', hexadecimal_number)

十六进制转N进制

1
2
3
4
5
6
7
8
9
hexadecimal_number = '2a'
decimal_number = int(hexadecimal_number, 16) # 十六进制转换为十进制
binary_number = bin(decimal_number) # 十进制转换为二进制
octal_number = oct(decimal_number) # 十进制转换为八进制

print('十六进制数:', hexadecimal_number)
print('转换为十进制:', decimal_number)
print('转换为二进制:', binary_number)
print('转换为八进制:', octal_number)

字符串(String)

访问字符串的值

Python 访问子字符串,可以使用方括号来截取字符串,如下实例:

1
2
3
4
5
6
7
var1 = 'Hello World!' 
var2 = "Python Runoob"

print(var1[0])
# H
print(var2[1:5])
# ytho

转义字符

在需要在字符中使用特殊字符时,python 用反斜杠\转义字符。如下表:

转义字符 描述
\(在行尾时) 续行符
\\ 反斜杠符号
\‘ 单引号
\“ 双引号
\a 响铃
\b 退格(Backspace)
\e 转义
\000
\n 换行
\v 纵向制表符
\t 横向制表符
\r 回车
\f 换页
\oyy 八进制数,y 代表 0~7 的字符,例如:\012 代表换行。
\xyy 十六进制数,以 \x 开头,yy代表的字符,例如:\x0a代表换行
\other 其它的字符以普通格式输出

如果不希望前置 \ 的字符转义成特殊字符,可以使用 原始字符串,在引号前添加 r 即可:

1
2
3
4
5
6
7
print('C:\some\name')  
# 这里\n的意思是换行
# C:\some
# ame
print(r'C:\some\name')
# 这里\n不会转义
C:\some\name

字符串运算符

下表实例变量 a 值为字符串 “Hello”,b 变量值为 “Python”:

操作符 描述 实例
+ 字符串连接 >>>a + b ‘HelloPython’
* 重复输出字符串 >>>a * 2 ‘HelloHello’
[] 通过索引获取字符串中字符 >>>a[1] ‘e’
[ : ] 截取字符串中的一部分 >>>a[1:4] ‘ell’
in 成员运算符 - 如果字符串中包含给定的字符返回 True >>>”H” in a True
not in 成员运算符 - 如果字符串中不包含给定的字符返回 True >>>”M” not in a True
r/R 原始字符串 - 原始字符串:所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。 原始字符串除在字符串的第一个引号前加上字母”r”(可以大小写)以外,与普通字符串有着几乎完全相同的语法。 >>>print r’\n’ \n >>> print R’\n’ \n

字符串格式化

基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。

字符串格式化符号:

符 号 描述
%c 格式化字符及其ASCII码
%s 格式化字符串
%d 格式化整数
%u 格式化无符号整型
%o 格式化无符号八进制数
%x 格式化无符号十六进制数
%X 格式化无符号十六进制数(大写)
%f 格式化浮点数字,可指定小数点后的精度
%e 用科学计数法格式化浮点数
%E 作用同%e,用科学计数法格式化浮点数
%g %f和%e的简写
%G %F 和 %E 的简写
%p 用十六进制数格式化变量的地址

实例:

1
2
print("My name is %s and weight is %d kg!" % ('Zara', 21))
# My name is Zara and weight is 21 kg!

多行字符串

三引号

Python 中三引号可以将复杂的字符串进行赋值。

Python 三引号允许一个字符串跨多行,字符串中可以包含换行符、制表符以及其他特殊字符。

三引号的语法是一对连续的单引号或者双引号(通常都是成对的用)。

1
2
3
4
5
6
7
8
s = """My Name is Pankaj.
I am the owner of JournalDev.com
JournalDev is a very popular website in Developers community."""

print(s)
# My Name is Pankaj.
# I am the owner of JournalDev.com
# JournalDev is a very popular website in Developers # # community.

续航符

1
2
3
4
5
6
sql = "select * " \
" from a " \
" where b = 1"

print(sql)
# select * from a where b = 1

括号

1
2
3
4
5
6
sql = ("select *"
" from a "
" where b = 1")

print(sql)
# select * from a where b = 1

内建函数

upper()、lower()、title()

将字符串进行大小写或标题化。

string.upper()

  • 转换 string 中的小写字母为大写

string.lower()

  • 转换 string 中所有大写字符为小写

string.title()

  • 返回”标题化”的 string,就是说所有单词都是以大写开始,其余字母均为小写
1
2
3
4
name = "Ada Lovelace" 
print(name.title()) # Ada Lovelace
print(name.upper()) # ADA LOVELACE
print(name.lower()) # ada lovelace

strip()

删除字符串的空格。

string.strip()

  • 在 string 上执行 lstrip()和 rstrip(),删除string首尾的空格

string.lstrip()

  • 截掉 string 开头的空格

string.rstrip()

  • 删除 string 末尾的空格
1
2
3
4
favorite_language = ' python '
favorite_language.rstrip() # ' python'
favorite_language.lstrip() # 'python '
favorite_language.strip() # 'python'

count()

返回 sub 在 string 里面出现的次数。

string.count(sub, start= 0, end=len(string))

  • sub – 搜索的子字符串
  • start – 字符串开始搜索的位置。默认为第一个字符,第一个字符索引值为0。
  • end – 字符串中结束搜索的位置。字符中第一个字符的索引为 0。默认为字符串的最后一个位置。
1
2
3
str="1011011"
print(str.count('0'))
## 2

split()

以 str 为分隔符截取字符串。

string.split(str="", num=string.count(str))

  • str – 分隔符,默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等。

  • num – 分割次数。默认为 -1, 即分隔所有

  • 返回值返回分割后的字符串列表

1
2
3
4
5
6
7
8
str = "this is string example....wow!!!" 
print (str.split( )) # 以空格为分隔符
print (str.split('i',1)) # 以 i 为分隔符分割一次
print (str.split('w')) # 以 w 为分隔符

# ['this', 'is', 'string', 'example....wow!!!']
# ['th', 's is string example....wow!!!']
# ['this is string example....', 'o', '!!!']
1
2
3
4
url = "http://www.baidu.com/python/image/123456.jpg"
path = url.split("/")
print(path[-1])
# 123456.jpg

replace()

string.replace(old, new[, max])

  • old – 将被替换的子字符串。
  • new – 新字符串,用于替换old子字符串。
  • max – 可选, 替换不超过 max 次
1
2
3
4
5
6
7
str = "www.w3cschool.cc"
print(str.replace("w3cschool.cc", "runoob.com")) )
# www.runoob.com

str = "is is is is"
print(str.replace("is", "was", 3))
# was was was is

find()

检测 str 是否包含在字符串string中。

string.find(str, beg=0, end=len(string))

  • str – 指定检索的字符串
  • beg – 开始索引,默认为0。
  • end – 结束索引,默认为字符串的长度。
1
2
3
4
5
6
str1 = "Runoob example....wow!!!" 
str2 = "exam";
print(str1.find(str2))
# 7
print(str1.find(str2, 10))
# -1

join()

join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。

string.join(sequence)

  • sequence – 要连接的元素序列。
1
2
3
4
5
6
7
8
s1 = "-"
s2 = ""
seq = ("r", "u", "n", "o", "o", "b")

print(s1.join( seq ))
# r-u-n-o-o-b
print(s2.join( seq ))
# runoob

isxxx()

判断字符串是否是字母或数字或空白

string.isalnum()

判断字符串是否是字母或数字

string.isalpha()

判断字符串是否是字母或中文字

string.isdigit()

判断字符串是否是数字

string.isnumeric()

判断字符串是否是数字字符

string.isspace()

判断字符串是否只包含空白

1
2
3
4
5
6
7
str1 = "runoob2016"  
print (str1.isalnum())
# True

str2 = "23443434"
print (str2.isnumeric())
# True

format()

基本语法是通过 {} 和 : 来代替以前的 % 。

format 函数可以接受不限个参数,位置可以不按顺序。

1
2
3
4
5
6
7
8
9
10
11
"{} {}".format("hello", "world")    
# 不设置指定位置,按默认顺序
# 'hello world'

"{0} {1}".format("hello", "world")
# 设置指定位置
# 'hello world'

"{1} {0} {1}".format("hello", "world")
# 设置指定位置
# 'world hello world'
1
2
3
4
5
6
7
8
9
10
11
12
print("网站名:{name}, 地址 {url}".format(name="菜鸟教程", url="www.runoob.com"))
# 网站名:菜鸟教程, 地址 www.runoob.com

# 通过字典设置参数
site = {"name": "菜鸟教程", "url": "www.runoob.com"}
print("网站名:{name}, 地址 {url}".format(site))
# 网站名:菜鸟教程, 地址 www.runoob.com

# 通过列表索引设置参数
my_list = ['菜鸟教程', 'www.runoob.com']
print("网站名:{0[0]}, 地址 {0[1]}".format(my_list)) # "0" 是必须的
# 网站名:菜鸟教程, 地址 www.runoob.com

数字格式化

下表展示了 str.format() 格式化数字的多种方法:

1
2
3
print("{:.2f}".format(3.1415926))
# 注意返回的是字符串
# '3.14'
数字 格式 输出 描述
3.1415926 {:.2f} 3.14 保留小数点后两位
3.1415926 {:+.2f} +3.14 带符号保留小数点后两位
-1 {:-.2f} -1.00 带符号保留小数点后两位
2.71828 {:.0f} 3 不带小数
5 {:0>2d} 05 数字补零 (填充左边, 宽度为2)
5 {:x<4d} 5xxx 数字补x (填充右边, 宽度为4)
10 {:x<4d} 10xx 数字补x (填充右边, 宽度为4)
1000000 {:,} 1,000,000 以逗号分隔的数字格式
0.25 {:.2%} 25.00% 百分比格式
1000000000 {:.2e} 1.00e+09 指数记法
13 {:>10d} 13 右对齐 (默认, 宽度为10)
13 {:<10d} 13 左对齐 (宽度为10)
13 {:^10d} 13 中间对齐 (宽度为10)
11 '{:b}'.format(11) '{:d}'.format(11) '{:o}'.format(11) '{:x}'.format(11) '{:#x}'.format(11) '{:#X}'.format(11) 1011 11 13 b 0xb 0XB 进制

^, <, > 分别是居中、左对齐、右对齐,后面带宽度

:号后面带填充的字符,只能是一个字符,不指定则默认是用空格填充。

+ 表示在正数前显示 +,负数前显示 - (空格)表示在正数前加空格

b、d、o、x 分别是二进制、十进制、八进制、十六进制。

此外我们可以使用大括号{}来转义大括号,如下实例:

1
2
print ("{} 对应的位置是 {{0}}".format("runoob"))
# runoob 对应的位置是 {0}

f-string

f-string 是 python3.6 之后版本添加的,称之为字面量格式化字符串,是新的格式化字符串的语法。

f-string 格式化字符串以 f 开头,后面跟着字符串,字符串中的表达式用大括号 {} 包起来,它会将变量或表达式计算后的值替换进去,实例如下:

1
2
3
4
5
6
7
8
9
name = 'Runoob'
print(f'Hello {name}') # 替换变量
# 'Hello Runoob'
print(f'{1+2}') # 使用表达式
# '3'

w = {'name': 'Runoob', 'url': 'www.runoob.com'}
print(f'{w["name"]}: {w["url"]}')
# 'Runoob: www.runoob.com'

列表(List)

列表由一系列按特定顺序排列的元素组成。你可以创建包含字母表中所有字母、数字0~9或所有家庭成员姓名的列表;也可以将任何东西加入列表中,其中的元素之间可以没有任何关系。

与元组对比,列表的长度可变、内容可以被修改。你可以用方括号定义,或用list函数:

1
2
3
4
list1 = ['physics', 'chemistry', 1997, 2000]
list2 = [1, 2, 3, 4, 5 ]
list3 = ["a", "b", "c", "d"]
list4 = list(('foo','bar','baz'))

list函数常用来在数据处理中实体化迭代器或生成器:

1
2
3
gen = range(10)
print(list(gen))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

访问列表值

在Python中,第一个列表元素的索引为0,而不是1。

Python为访问最后一个列表元素提供了一种特殊语法。通过将索引指定为-1,可让Python返回最后一个列表元素:

1
2
3
4
5
6
7
8
list1 = ['physics', 'chemistry', 1997, 2000]
list2 = [1, 2, 3, 4, 5, 6, 7]

list1[0] # physics
list1[2:] # [1997, 2000]
list2[1:5] # [2, 3, 4, 5]
list2[:] # [1, 2, 3, 4, 5, 6, 7]
list2[-1] # 7

切片

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

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

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

1
2
3
4
5
seq = [7, 2, 3, 6, 3, 5, 6, 0, 1]
seq[:5]
# [7, 2, 3, 6, 3]
seq[-4:]
# [5, 6, 0, 1]

下图展示了正整数和负整数的切片。在图中,指数标示在边缘以表明切片是在哪里开始哪里结束的。

Python切片演示

在第二个冒号后面使用step,可以隔一个取一个元素:

1
2
seq[::2]
# [7, 3, 3, 6, 1]

一个聪明的方法是使用-1,它可以将列表或元组颠倒过来:

1
2
seq[::-1]
# [1, 0, 6, 5, 3, 6, 3, 2, 7]

添加元素

list.append(obj)

  • 用于在列表末尾添加新元素。
1
2
3
motorcycles = ['honda', 'yamaha', 'suzuki'] 
motorcycles.append('ducati')
# ['honda', 'yamaha', 'suzuki', 'ducati']

插入元素

list.insert(index, obj)

  • 用于将指定对象插入列表的指定位置。为此,你需要指定新元素的索引和值。
1
2
3
motorcycles = ['honda', 'yamaha', 'suzuki'] 
motorcycles.insert(0, 'ducati')
# ['ducati', 'honda', 'yamaha', 'suzuki']
1
2
3
aList = [123, 'xyz', 'zara', 'abc']
aList.insert(3, 2009)
# [123, 'xyz', 'zara', 2009, 'abc']

警告:与append相比,insert耗费的计算量大,因为对后续元素的引用必须在内部迁移,以便为新元素提供空间。如果要在序列的头部和尾部插入元素,你可能需要使用collections.deque,一个双尾部队列。

删除元素

del 语句

使用 del 语句可以从一个列表中根据索引来删除一个元素,而不是值来删除元素。这与使用pop() 返回删除的元素值不同。

1
2
3
motorcycles = ['honda', 'yamaha', 'suzuki'] 
del motorcycles[1]
print(motorcycles) # ['honda', 'suzuki']

list.remove(obj)

  • 用于移除列表中某个值的第一个匹配项。

  • remove()只删除第一个指定的值。如果要删除的值可能在列表中出现多次,就需要使用循环来判断是否删除了所有这样的值。

1
2
3
aList = [123, 'xyz', 'zara', 'abc', 'xyz']
aList.remove('xyz')
# [123, 'zara', 'abc', 'xyz']
1
2
3
4
5
6
7
8
aList = [123, 'xyz', 'zara', 'abc','xyz','xyz','xyz']
while True:
if 'xyz' in aList:
aList.remove('xyz')
else:
break
print(aList)
# [123, 'zara', 'abc']

list.pop([index=-1])

  • 用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
1
2
3
4
motorcycles = ['honda', 'yamaha', 'suzuki'] 
popped_motorcycle = motorcycles.pop()
print(motorcycles) # ['honda', 'yamaha']
print(popped_motorcycle) # suzuki
1
2
3
4
list1 = ['Google', 'Runoob', 'Taobao']
list_pop = list1.pop(1)
print(list_pop) # Runoob
print(list1) # ['Google', 'Taobao']

list.clear()

  • 用于清空列表,类似于 **del a[:]**。
1
2
3
4
list1 = ['Google', 'Runoob', 'Taobao', 'Baidu']
list1.clear()
print (list1)
# []

扩展列表

list.extend(seq)

  • 用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
  • seq – 元素列表,可以是列表、元组、集合、字典,若为字典,则仅会将键(key)作为元素依次添加至原列表的末尾。
1
2
3
4
5
6
list1 = ['Google', 'Runoob', 'Taobao'] 
list2 = list(range(5)) # 创建 0-4 的列表
list1.extend(list2) # 扩展列表

print (list1)
# ['Google', 'Runoob', 'Taobao', 0, 1, 2, 3, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 语言列表 
language = ['French', 'English', 'German']
# 元组
language_tuple = ('Spanish', 'Portuguese')
# 集合
language_set = {'Chinese', 'Japanese'}

# 添加元组元素到列表末尾
language.extend(language_tuple)
print(language)
# ['French', 'English', 'German', 'Spanish', 'Portuguese']

# 添加集合元素到列表末尾
language.extend(language_set)
print(language)
# ['French', 'English', 'German', 'Spanish', 'Portuguese', 'Chinese', 'Japanese']

组织排序

list.sort(cmp=None, key=None, reverse=False)

  • sort()函数用于对原列表进行原地排序(不创建新的对象),如果指定参数,则使用比较函数指定的比较函数。
1
2
3
4
cars = ['bmw', 'audi', 'toyota', 'subaru'] 
cars.sort()
print(cars)
# ['audi', 'bmw', 'subaru', 'toyota']
1
2
3
4
vowels = ['e', 'a', 'u', 'o', 'i']
vowels.sort(reverse=True)
print(vowels)
# ['u', 'o', 'i', 'e', 'a']
1
2
3
4
5
b = ['saw', 'small', 'He', 'foxes', 'six']
#对列表按照元素的长度进行排序
b.sort(key=len)
print(b)
# ['He', 'saw', 'six', 'small', 'foxes']

反转顺序

list.reverse()

  • reverse() 函数用于反向列表中元素。方法reverse()永久性地修改列表元素的排列顺序。
1
2
3
aList = [123, 'xyz', 'zara', 'abc', 'xyz']
aList.reverse()
print(aList) # ['xyz', 'abc', 'zara', 'xyz', 123]

获取列表长度

len(list)

  • 使用函数len()可快速获悉列表的长度。
1
2
cars = ['bmw', 'audi', 'toyota', 'subaru']
print(len(cars)) # 4

匹配位置

list.index(x[, start[, end]])

  • x– 查找的对象。
  • start– 可选,查找的起始位置。
  • end– 可选,查找的结束位置。
1
2
3
list1 = ['Google', 'Runoob', 'Taobao']
print(list1.index('Taobao'))
## 1

脚本操作符

Python 表达式 结果 描述
len([1, 2, 3]) 3 长度
[1, 2, 3] + [4, 5, 6] [1, 2, 3, 4, 5, 6] 组合
[‘Hi!’] * 4 [‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’] 重复
3 in [1, 2, 3] True 元素是否存在于列表中
for x in [1, 2, 3]: print x, 1 2 3 迭代

列表推导式

列表推导式格式为:

[表达式 for 变量 in 列表]
[out_exp_res for out_exp in input_list]

或者

[表达式 for 变量 in 列表 if 条件]
[out_exp_res for out_exp in input_list if condition]

  • out_exp_res:列表生成元素表达式,可以是有返回值的函数。
  • for out_exp in input_list:迭代 input_list 将 out_exp 传入到 out_exp_res 表达式中。
  • if condition:条件语句,可以过滤列表中不符合条件的值。

实例

1
2
3
4
5
names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
new_names = [name.upper() for name in names if len(name)>3]

print(new_names)
# ['ALICE', 'JERRY', 'WENDY', 'SMITH']
1
2
3
multiples = [i for i in range(30) if i % 3 == 0]
print(multiples)
# [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

元组(Tuple)

Python 的元组与列表类似,不同之处在于元组的元素不能修改。

创建元组

元组是一个固定长度,不可改变的Python序列对象。创建元组的最简单方式,是用逗号分隔一列值:

1
2
3
4
5
6
7
8
# 小括号
tup1 = ('physics', 'chemistry', 1997, 2000)

# 内置函数tuple()
tup2 = tuple(('Python', 'Java', 'C#'))

# 任意无符号的对象,以逗号隔开,默认为元组
tup3 = "a", "b", "c", "d"

创建空元组

1
2
tup1 = ()
tup2 = tuple()

元组中只包含一个元素时,需要在元素后面添加逗号

1
2
3
4
5
6
tup1 = (1,)
tup2 = (1)
print(type(tup1))
# <class 'tuple'>
print(type(tup2))
# <class 'int'>

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

1
2
3
4
5
6
print(tuple([4, 0, 2]))
# (4, 0, 2)

tup = tuple('string')
print(tup)
# ('s', 't', 'r', 'i', 'n', 'g')

修改元组

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

1
2
3
4
5
6
7
8
tup = tuple(['foo', [1, 2], True])

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

元组中的元素值是不允许修改的,但我们可以对元组用加法运算符进行连接组合

1
2
3
4
5
6
7
8
tup1 = (12, 34.56) 
tup2 = ('abc', 'xyz')

# 创建一个新的元组
tup3 = tup1 + tup2

print(tup3)
# (12, 34.56, 'abc', 'xyz')

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

1
2
3
4
tup = tuple(['foo', [1, 2], True])
tup[1].append(3)
print(tup)
# ('foo', [1, 2, 3], True)

删除元组

元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组,如下实例:

1
2
tup = ('physics', 'chemistry', 1997, 2000)  
del tup

访问元组

| Python 表达式 |描述 |
| :———— | :—————- | :————————— |
| L[2] | 读取第三个元素 |
| L[-2] | 反向读取,读取倒数第二个元素 |
| L[1:] | 截取元素 |

迭代元组

元组是可迭代对象,可以使用for…in进行遍历

1
2
3
4
t = tuple(('Python', 'Java', 'C#'))
for item in t:
print(item)
# Python Java C#

拆分元祖

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

1
2
3
4
tup = (4, 5, 6)
a, b, c = tup
print(c)
# 6

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

1
2
3
4
tup = 4, 5, (6, 7)
a, b, (c, d) = tup
print(d)
# 7

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

1
2
3
4
5
6
7
seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
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

Python允许从元组的开头“摘取”几个元素。它使用了特殊的语法*rest,这也用在函数签名中以抓取任意长度列表的位置参数:

1
2
3
4
values = 1, 2, 3, 4, 5
a, b, *rest = values
print(rest)
# [3, 4, 5]

rest的部分是想要舍弃的部分,rest的名字不重要。作为惯用写法,许多Python程序员会将不需要的变量使用下划线:

1
a, b, *_ = values

因为元组的大小和内容不能修改,它的实例方法都很轻量。其中一个很有用的就是count(也适用于列表),它可以统计某个值得出现频率:

1
2
3
a = (1, 2, 2, 2, 3, 4, 2)
print(a.count(2))
# 4

运算符

Python 表达式 结果 描述
len((1, 2, 3)) 3 计算元素个数
(1, 2, 3) + (4, 5, 6) (1, 2, 3, 4, 5, 6) 连接
(‘Hi!’,) * 4 (‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’) 复制
3 in (1, 2, 3) True 元素是否存在
for x in (1, 2, 3): print x, 1 2 3 迭代

元组推导式

元组推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的元组。

元组推导式基本格式:

(expression for item in Sequence )

(expression for item in Sequence if conditional )

实例

1
2
3
4
5
6
7
a = (x for x in range(1,10))
>>> a
<generator object <genexpr> at 0x7faf6ee20a50>
# 返回的是生成器对象

tuple(a) # 使用 tuple() 函数,可以直接将生成器对象转换成元组
#(1, 2, 3, 4, 5, 6, 7, 8, 9)

字典(Dict)

字典是另一种可变容器模型,且可存储任意类型对象。

字典的每个键值 key:value 对用冒号 : 分割,每个键值对之间用逗号 , 分割,整个字典包括在花括号 {} 中 。

注意:dict 作为 Python 的关键字和内置函数,变量名不建议命名为 dict

1
2
3
4
tinydict = {'a': 1, 'b': 2, 'b': '3'}

print(tinydict['b'])
# '3'

访问字典中的值

把相应的键放入熟悉的方括弧,如下实例:

1
2
3
4
5
tinydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
print(tinydict['Name'])
# Zara
print(tinydict['Age'])
# 7

修改字典

如果键存在则修改对应的值,如果键不存在则新增这个键和值。

向字典添加新内容的方法是增加新的键/值对,修改或删除已有键/值对如下实例:

1
2
3
4
5
6
7
tinydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}  

tinydict['Age'] = 8
# 更新

tinydict['School'] = "RUNOOB"
# 添加

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

1
2
'Age' in tinydict	
# True

删除字典元素

del

对于字典中不再需要的信息,可使用del语句将相应的键—值对彻底删除。使用del语句时,必须指定字典名和要删除的键。

1
2
3
alien_0 = {'color': 'green', 'points': 5} 

del alien_0['points']

dict.pop(key[,default])

字典 pop() 方法删除字典 key(键)所对应的值,返回被删除的值。

  • key - 要删除的键
  • default - 当键 key 不存在时返回的值
1
2
3
4
5
alien_0 = {'color': 'green', 'points': 5}
print(alien_0.pop('points'))
# 5
print('alien_0')
# alien_0

清空字典

dict.clear()

1
2
3
tinydict = {'Name': 'Runoob', 'Age': 7, 'Class': 'First'}  
tinydict.clear() # 清空字典
del tinydict # 删除字典

遍历字典

遍历所有键值对

dict.items()

以列表返回可遍历的(键, 值) 元组数组。

注意,即便遍历字典时,键—值对的返回顺序也与存储顺序不同。Python不关心键—值对的存储顺序,而只跟踪键和值之间的关联关系。

1
2
3
4
5
6
7
8
9
10
11
12
user_0 = { 
'username': 'efermi',
'first': 'enrico',
'last': 'fermi',
}

for key, value in user_0.items():
print(key, value)

# username efermi
# first enrico
# last fermi

遍历所有键

dict.keys()

返回一个字典所有的键。方法keys()并非只能用于遍历;实际上,它返回一个列表,其中包含字典中的所有键。

1
2
3
4
5
6
7
8
9
10
favorite_languages = { 
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}

for name in favorite_languages.keys():
# for name in favorite_languages:
print(name.title())

遍历字典时,会默认遍历所有的键,因此,如果将上述代码中的for name in favorite_ languages.keys(): 替换为 for name in favorite_languages:,输出将不变。

遍历所有值

dict.values()

以列表返回一个字典所有的值。

1
2
3
4
5
6
7
8
9
favorite_languages = { 
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}

for language in favorite_languages.values():
print(language.title())

融合字典

dict.update(dict2)

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

1
2
3
4
d1 = {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
d1.update({'b' : 'foo', 'c' : 12})
print(d1)
# {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}

update方法是原地改变字典,因此任何传递给update的键的旧的值都会被舍弃。

用序列创建字典

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

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

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

1
2
3
4
5
key = range(5)
value = reversed(range(5))
mapping = dict(zip(key,value))
print(mapping)
# {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

字典推导式

字典推导基本格式

{ key_expr: value_expr for value in collection }

{ key_expr: value_expr for value in collection if condition }

实例

1
2
3
4
5
listdemo = ['Google','Runoob', 'Taobao']
# 将列表中各字符串值为键,各字符串的长度为值,组成键值对
newdict = {key:len(key) **for** key **in** listdemo}
print(newdict)
# {'Google': 6, 'Runoob': 6, 'Taobao': 6}
1
2
3
4
dic = {x: x**2 for x in (2, 4, 6)}

print(dic)
# {2: 4, 4: 16, 6: 36}

字典键的特性

字典值可以没有限制地取任何 python 对象,既可以是标准的对象,也可以是用户定义的,但键不行。

两个重要的点需要记住:

  • 不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住,如下实例:
1
2
3
tinydict = {'Name': 'Runoob', 'Age': 7, 'Name': 'Manni'}  
print(tinydict['Name'])
# Manni
  • 键必须不可变,所以可以用数字,字符串或元组充当,不可以是列表,如下实例:
1
2
3
4
tinydict = {['Name']: 'Zara', 'Age': 7} 

print(tinydict['Name'])
# TypeError: unhashable type: 'list'

内置方法

get()

get() 函数返回指定键的值。

dict.get(key[, value])

  • key – 字典中要查找的键。

  • value – 可选,如果指定键的值不存在时,返回该默认值。

1
2
3
4
5
6
7
8
9
10
11
tinydict = {'Name': 'Runoob', 'Age': 27}
print(tinydict.get('Age'))
# 27

# 没有设置 Sex,也没有设置默认的值,输出 None
print (tinydict.get('Sex'))
# None

# 没有设置 Salary,输出默认的值 0.0
print (tinydict.get('Salary', 0.0))
# 0.0

get() 方法 Vs dict[key] 访问元素区别

  • get(key) 方法在 key(键)不在字典中时,可以返回默认值 None 或者设置的默认值。

  • dict[key] 在 key(键)不在字典中时,会触发 KeyError 异常。

1
2
3
4
5
6
7
8
runoob = {}
print(runoob.get('url')) # 返回 None
# None

print(runoob['url']) # 触发 KeyError
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# KeyError: 'url'

keys()

字典 keys() 方法返回一个视图对象。视图对象不是列表,不支持索引,可以使用 list() 来转换为列表。

我们不能对视图对象进行任何的修改,因为字典的视图对象都是只读的。

dict.keys()

实例

1
2
3
4
5
6
7
8
dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
keys = dishes.keys()

print(keys)
# dict_keys(['eggs', 'sausage', 'bacon', 'spam'])

print(list(keys))
# ['eggs', 'sausage', 'bacon', 'spam']

values()

字典 values() 方法返回一个视图对象。视图对象不是列表,不支持索引,可以使用 list() 来转换为列表。

我们不能对视图对象进行任何的修改,因为字典的视图对象都是只读的。

dict.values()

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
values = dishes.values()

print(values)
# dict_values([2, 1, 1, 500])

print(list(values))
#
.[2, 1, 1, 500]

# 迭代
n = 0
for val in values:
n += val
print(n)
# 504

items()

items() 方法以列表返回视图对象,是一个可遍历的key/value 对。视图对象不是列表,不支持索引,可以使用 list() 来转换为列表。

我们不能对视图对象进行任何的修改,因为字典的视图对象都是只读的。

dict.items()

实例

1
2
3
tinydict = {'Name': 'Runoob', 'Age': 7}  
print(tinydict.items())
# dict_items([('Name', 'Runoob'), ('Age', 7)])

集合(Set)

集合(set)是一个无序不重复元素序列。

可以使用大括号 { } 或者 set() 函数创建集合,

**创建一个空集合必须用 set() 而不是 { }**,因为 { } 是用来创建一个空字典。

集合的特点

  • 自动去除重复数据
  • 顺序随机,不支持下标

创建集合

parame = {value01,value02,...}
或者
set(value)

实例

1
2
3
4
5
basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
print(basket)

# 这里演示的是去重功能
# {'orange', 'banana', 'pear', 'apple'}

添加元素

set.add(x)

将元素x添加到集合s中,如果元素已存在,则不进行任何操作。

1
2
3
4
5
thisset = set(("Google", "Runoob", "Taobao"))
thisset.add("Facebook")

print(thisset)
# {'Taobao', 'Facebook', 'Google', 'Runoob'}

set.update(set)

update() 方法用于修改当前集合,可以添加新的元素或集合到当前集合中,如果添加的元素在集合中已存在,则该元素只会出现一次,重复的会忽略。

1
2
3
4
5
6
x = {"apple", "banana", "cherry"} 
y = {"google", "runoob", "apple"}
x.update(y)

print(x)
## {'banana', 'apple', 'google', 'runoob', 'cherry'}

删除元素

set.remove(item)

remove() 方法用于移除集合中的指定元素。remove()方法在移除一个不存在的元素时会发生错误。

1
2
3
4
5
6
7
8
9
10
fruits = {"apple", "banana", "cherry"}  

fruits.remove("banana")
print(fruits)
# {'cherry', 'apple'}

fruits.remove("melon")
#Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'melon'

set.discard(value)

discard() 方法用于移除指定的集合元素。

该方法不同于 remove() 方法,因为 remove() 方法在移除一个不存在的元素时会发生错误,而 discard() 方法不会。

1
2
3
4
fruits = {"apple", "banana", "cherry"}  
fruits.discard("banana")
print(fruits)
# {'cherry', 'apple'}

set.pop()

pop()方法用于随机移除一个元素。

1
2
3
4
fruits = {"apple", "banana", "cherry"}  
fruits.pop()
print(fruits)
# {'apple', 'banana'} 也可能是 {'cherry', 'banana'} 或者 {'cherry', 'banana'}

判断元素是否在集合中存在

x (not)in s

判断元素 x 是否在集合 s 中,存在返回 True,不存在返回 False。

1
2
3
4
5
thisset = set(("Google", "Runoob", "Taobao"))
print("Runoob" in thisset)
# True
print("Facebook" in thisset)
# False

集合运算

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

1
2
a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7, 8}

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

1
2
3
4
print(a.union(b))
# {1, 2, 3, 4, 5, 6, 7, 8}
print(a|b)
# {1, 2, 3, 4, 5, 6, 7, 8}

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

1
2
3
4
print(a.intersection(b))
# {3,4,5}
print(a & b)
# {3,4,5}

表3-1列出了常用的集合方法。

表3-1 Python的集合操作

集合推导式

集合推导式基本格式:

{ expression for item in Sequence }

{ expression for item in Sequence if conditional }

实例

1
2
3
setnew = {i**2 for i in (1,2,3)}
print(setnew)
# {1, 4, 9}
1
2
3
a = {x for x in 'abracadabra' if x not in 'abc'}
print(a)
# {'d', 'r'}

函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

你可以定义一个由自己想要功能的函数,以下是简单的规则:

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号**()**。
  • 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
1
2
3
4
def functionname( parameters ):  
"函数_文档字符串"
function_suite
return [expression]

可更改(mutable)与不可更改(immutable)对象

在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

  • 不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
  • 可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

  • 不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
  • 可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响

python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

传不可变对象实例

1
2
3
4
5
6
7
def ChangeInt( a ):    
a = 10
b = 2
ChangeInt(b)

print(b)
# 结果是 2

实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。

传可变对象实例

1
2
3
4
5
6
7
8
9
# 可写函数说明 def changeme( mylist ):   
"修改传入的列表"
mylist.append([1,2,3,4])
print "函数内取值: ", mylist
return
# 调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print "函数外取值: ", mylist

实例中传入函数的和在末尾添加新内容的对象用的是同一个引用,故输出结果如下:

1
2
函数内取值:  [10, 20, 30, [1, 2, 3, 4]]
函数外取值: [10, 20, 30, [1, 2, 3, 4]]

可变参数

位置参数(positional argument)

所谓位置参数,是指用相对位置指代参数。

1
2
3
4
def functionname([formal_args,] *var_args_tuple ):   
"函数_文档字符串"
function_suite
return [expression]

*参数收集所有未匹配的位置参数组成一个tuple对象,局部变量args指向此tuple对象

位置参数实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
def printinfo( arg1, *vartuple):   
"打印任何传入的参数"
print arg1
for var in vartuple:
print var
return

# 调用printinfo 函数
printinfo( 10 )
# 10
printinfo( 70, 60, 50 )
# 70 60 50

关键字参数(keyword argument)

使用关键字指代参数。

1
2
3
4
def functionname([formal_args,]  **var_args_dict ):   
"函数_文档字符串"
function_suite
return [expression]

**参数收集所有未匹配的关键字参数组成一个dict对象,局部变量kwargs指向此dict对象

关键词参数实例如下:

1
2
3
4
5
6
7
def bar(param1, **param2):
print(param1)
print(param2)

bar(1,a=2,b=3)
# 1
# {'a': 2, 'b': 3}

匿名(lambda)函数

python 使用 lambda 来创建匿名函数。

  • lambda只是一个表达式,函数体比def简单很多。
  • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
  • lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
  • 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

语法:

lambda [arg1 [,arg2,.....argn]]:expression

实例:

1
2
3
sum = lambda arg1, arg2: arg1 + arg2
print(sum(10,20))
# 30
1
2
print((lamda a, b, c=5: a+b+c)(2,6))
# 13

内置函数

print()

print()方法用于打印输出

print(objects, sep=' ', end='\n', file=sys.stdout, flush=False)

  • objects – 复数,表示可以一次输出多个对象。输出多个对象时,需要用 , 分隔。
  • sep – 用来间隔多个对象,默认值是一个空格
  • end – 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符串。
  • file – 要写入的文件对象。
  • flush – 输出是否被缓存通常决定于 file,但如果 flush 关键字参数为 True,流会被强制刷新。
1
2
3
4
5
6
str1 = '111'
str2 = '222'
print(str1,str2,sep='\n')

# 111
# 222

eval()

eval() 函数用来执行一个字符串表达式,并返回表达式的值。

eval(expression[, globals[, locals]])

  • expression – 表达式。
  • globals – 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
  • locals – 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
1
2
3
4
5
6
7
8
9
10
print(eval('2+2'))
# 4
data = ""

raw_data = "[{'姓名':'张三','年龄':14},{'姓名':'李四','年龄':25}]"
data = eval(raw_data)
print(data)
# [{'姓名': '张三', '年龄': 14}, {'姓名': '李四', '年龄': 25}]
print(type(data))
# <class 'list'>

enumerate()

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

enumerate(sequence, [start=0])

实例

1
2
3
4
5
6
7
8
seasons = ['Spring', 'Summer', 'Fall', 'Winter']

print(list(enumerate(seasons)))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

print(list(enumerate(seasons, start=1)))
# 下标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

for循环使用enumerate

1
2
3
4
5
6
7
8
9
10
11
12
seq = ['one', 'two', 'three'] 
for i, j in enumerate(seq):
print(i, j)
# 0 one
# 1 two
# 2 three

for element in enumerate(seq):
print(element)
# (0, 'one')
# (1, 'two')
# (2, 'three')

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

1
2
3
4
5
6
some_list = ['foo','bar','baz']
mapping = {}
for i, v in enumerate(some_list):
mapping[v] = i
print(mapping)
# {'foo': 0, 'bar': 1, 'baz': 2}

filter()

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

filter(function, iterable)

1
2
3
4
tmplist = filter(lambda n: n % 2 == 1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 
newlist = list(tmplist)
print(newlist)
# [1,3,5,7,9]

map()

map() 函数会根据提供的函数对指定序列做映射。

第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

map(function, iterable, ...)

  • function – 函数名!!!
  • iterable – 一个或多个序列
  • 返回一个迭代器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def square(x):  # 计算平方数
    return x ** 2

    print(map(square, [1, 2, 3, 4, 5])) # 计算列表各个元素的平方
    # <map object at 0x100d3d550> # 返回迭代器

    print(list(map(square, [1, 2, 3, 4, 5]))) # 使用 list() 转换为列表
    # [1, 4, 9, 16, 25]

    print(list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))) # 使用 lambda 匿名函数
    # [1, 4, 9, 16, 25]
    输入一行整数,数字之间以空格间隔。输出这些数字组成的完整列表。
1
2
3
4
5
6
7
8
9
str = "1 2 3 4 5"
num = str.split()

print(list(map(int, num)))
# map映射
# [1, 2, 3, 4, 5]
print([int(i) for i in num])
# 列表推导式
# [1, 2, 3, 4, 5]

sorted()

sorted(iterable, key=None, reverse=False)

  • sorted()函数让你能够按特定顺序显示列表元素,同时不影响它们在列表中的原始排列顺序
  • iterable – 可迭代对象。
  • key – 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
  • reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
  • 返回值返回重新排序的列表
1
2
3
4
5
cars = ['bmw', 'audi', 'toyota', 'subaru'] 
print(sorted(cars))
# ['audi', 'bmw', 'subaru', 'toyota']
print(cars)
# ['bmw', 'audi', 'toyota', 'subaru']

zip()

zip([iterable, ...])

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。

我们可以使用 list() 转换来输出列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zipped = zip(a,b)
# 返回一个对象<zip object at 0x000001D2A6F817C0>

print(list(zipped)) # list() 转换为列表
# [(1, 4), (2, 5), (3, 6)]

print(list(zip(a,c))) # 元素个数与最短的列表一致
# [(1, 4), (2, 5), (3, 6)]

a1, a2 = zip(*zip(a,b)) # 与 zip 相反,zip(*) 可理解为解压,返回二维矩阵式

print(list(a1))
# [1, 2, 3]

print(list(a2))
# [4, 5, 6]

给出一个“被压缩的”序列,zip可以被用来解压序列。也可以当作把行的列表转换为列的列表。这个方法看起来有点神奇:

1
2
3
4
5
6
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'),('Schilling', 'Curt')]
first_names, last_names = zip(*pitchers)
print(first_names)
# ('Nolan', 'Roger', 'Schilling')
print(last_names)
# ('Ryan', 'Clemens', 'Curt')

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

1
2
3
4
5
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

reversed()

reversed(seq)

reversed 函数返回一个反转的迭代器。

  • seq – 要转换的序列,可以是 tuple, string, list 或 range。
1
2
print(list(reversed(range(10))))
# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

要记住reversed是一个生成器,只有实体化(即列表或for循环)之后才能创建翻转的序列。

setattr()

setattr(object, name, value)

setattr() 函数对应函数 getattr(),用于设置属性值,该属性不一定是存在的。

如果属性不存在会创建一个新的对象属性,并对属性赋值。

  • object – 对象。
  • name – 字符串,对象属性。
  • value – 属性值。
1
2
3
4
5
6
7
8
class A(object):
bar = 1
a = A()
print(getattr(a, 'bar')) # 获取属性 bar 值
## 1
print(setattr(a, 'bar', 5)) # 设置属性 bar 值
print(a.bar)
## 5

hasattr()

hasattr(object, name)

hasattr() 函数用于判断对象是否包含对应的属性。

  • object – 对象。
  • name – 字符串,属性名。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Coordinate:    
x = 10
y = -5
z = 0
point1 = Coordinate()
print(hasattr(point1, 'x'))
# True
print(hasattr(point1, 'y'))
# True
print(hasattr(point1, 'z'))
# True
print(hasattr(point1, 'no')) # 没有该属性
# False

getattr()

getattr(object, name[, default])

getattr() 函数用于返回一个对象属性值。

  • object – 对象。
  • name – 字符串,对象属性。
  • default – 默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError。
1
2
3
4
5
6
7
8
9
10
class A(object): 
bar = 1

a = A()
getattr(a, 'bar') # 获取属性 bar 值
# 1
getattr(a, 'bar2') # 属性 bar2 不存在,触发异常
# Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute 'bar2'
getattr(a, 'bar2', 3) # 属性 bar2 不存在,但设置了默认值 3
# 3

三元表达式

python中的三元操作符可以使用 if-else 语句也就是条件操作符的一个快捷方式:

value = true-expr if condition else false-expr

  • true-exprfalse-expr可以是任何Python代码。它和下面的代码效果相同:
1
2
3
4
if condition:
value = true-expr
else:
value = false-expr

举例:

1
2
3
4
5
6
7
8
sex = 1
label = '男' if sex == 1 else '女'
print(label)
# 男

x = 5
print('Non-negative' if x >= 0 else 'Negative')
# Non-negative

文件处理

文件打开

Python open() 方法用于打开一个文件,并返回文件对象

在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。最常使用的是两个位置参数和一个关键字参数:open(filename, mode, encoding=None)

f = open('workfile', 'w', encoding="utf-8")

完整的语法格式为:

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

mode 参数有:

模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
t 文本模式 (默认)。
b 二进制模式。
+ 打开一个文件进行更新(可读可写)。
U 通用换行模式(Python 3 不支持)。
x 写模式,新建一个文件,如果该文件已存在则会报错。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
  • b二进制;+可读可写
  • rrbr+rb+: 只要文件不存在就报错,指针放在文件开头
  • wwbw+wb+: 只要文件不存在就新建文件,指针放在文件开头,用新内容覆盖原内容
  • aaba+ab+: 只要文件不存在就新建文件,指针放在文件结尾

With关键字

在处理文件对象时,最好使用 with 关键字。优点是,子句体结束后,文件会正确关闭,即便触发异常也可以。而且,使用 with 相比等效的 try-finally代码块要简短得多:

1
2
3
4
5
6
with open('workfile', encoding="utf-8") as f:
read_data = f.read()

# We can check that the file has been automatically closed.
f.closed
# True

警告:调用 f.write() 时,未使用 with 关键字,或未调用 f.close(),即使程序正常退出,也可能导致 f.write() 的参数没有完全写入磁盘。

文件对象读写

file.read()

read() 方法用于从文件读取指定的字符数(文本模式 t)或字节数(二进制模式 b),如果未给定参数 sizesize 为负数则读取文件所有内容。如已到达文件末尾,f.read() 返回空字符串('')。

1
2
3
4
5
6
# 打开文件
fo = open("test.txt", "r+")
print(fo.read())
# This is the entire file.\n
print(fo.read())
# ''

file.readline()

f.readline()从文件中读取单行数据;字符串末尾保留换行符(\n),只有在文件不以换行符结尾时,文件的最后一行才会省略换行符。这种方式让返回值清晰明确;只要 f.readline()返回空字符串,就表示已经到达了文件末尾,空行使用 '\n' 表示,该字符串只包含一个换行符。

1
2
3
4
5
6
f.readline()
# 'This is the first line of the file.\n'
f.readline()
# 'Second line of the file\n'
f.readline()
# ''

从文件中读取多行时,可以用循环遍历整个文件对象。这种操作能高效利用内存,快速,且代码简单:

1
2
3
4
for line in f:
print(line, end='')
# This is the first line of the file.
# Second line of the file

file.readlines()

readlines() 方法用于读取所有行(直到结束符 EOF)并返回列表,该列表可以由 Python 的 for… in … 结构进行处理。 如果碰到结束符 EOF 则返回空字符串。

1
2
3
4
5
6
7
f.readlines()
# ['This is the first line of the file\n', 'Second line of the file']

for line in f.readlines():
print(line, end='')
# This is the first line of the file.
# Second line of the file

如需以列表形式读取文件中的所有行,可以用 list(f)f.readlines()

file.write()

write() 方法用于向文件中写入指定字符串,并返回写入的字符数。

在文件关闭前或缓冲区刷新前,字符串内容存储在缓冲区中,这时你在文件中是看不到写入的内容的。

1
2
r = f.write('This is a test\n')
# 15

file.tell()

返回整数,给出文件对象在文件中的当前位置,表示为二进制模式下时从文件开始的字节数,以及文本模式下的意义不明的数字。

file.seek()

seek() 方法用于移动文件读取指针到指定位置。

f.seek(offset, whence)

  • offset – 开始的偏移量,也就是代表需要移动偏移的字节数,如果是负数表示从倒数第几位开始。
  • whence:可选,默认值为 0。给 offset 定义一个参数,表示要从哪个位置开始偏移;0 代表从文件开头开始算起,1 代表从当前位置开始算起,2 代表从文件末尾算起。
1
2
3
4
5
6
7
8
9
10
11
f = open('workfile', 'rb+')
f.write(b'0123456789abcdef')
# 16
f.seek(5) # 移动到文件的第六个字节
# 5
f.read(1)
# b'5'
f.seek(-3, 2) # 移动到文件倒数第三个字节
# 13
f.read(1)
# b'd'

NumPy

NumPy是一个功能强大的Python库,主要用于对多维数组执行计算。NumPy这个词来源于两个单词– NumericalPython

Ndarray 对象

NumPy 最重要的一个特点是其 N 维数组对象 ndarray,它是一系列同类型数据的集合,以 0 下标为开始进行集合中元素的索引。

ndarray 内部由以下内容组成:

  • 一个指向数据(内存或内存映射文件中的一块数据)的指针。
  • 数据类型或 dtype,描述在数组中的固定大小值的格子。
  • 一个表示数组形状(shape)的元组,表示各维度大小的元组。
  • 一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要”跨过”的字节数。

img

numpy.array

numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)

参数说明:

名称 描述
object 数组或嵌套的数列
dtype 数组元素的数据类型,可选
copy 对象是否需要复制,可选
order 创建数组的样式,C为行方向,F为列方向,A为任意方向(默认)
subok 默认返回一个与基类类型一致的数组
ndmin 指定生成数组的最小维度
1
2
3
4
5
6
7
8
9
import numpy as np 
my_array_1 = np.array([1,2,3])
print(my_array_1)
# [1 2 3]

my_array_2 = np.array([[1, 2], [3, 4]])
print(my_array_2)
# [[1 2]
# [3 4]]

数组属性

NumPy 数组的维数称为秩(rank),秩就是轴的数量,即数组的维度,一维数组的秩为 1,二维数组的秩为 2,以此类推。

在 NumPy中,每一个线性的数组称为是一个轴(axis),也就是维度(dimensions)。比如说,二维数组相当于是两个一维数组,其中第一个一维数组中每个元素又是一个一维数组。所以一维数组就是 NumPy 中的轴(axis),第一个轴相当于是底层数组,第二个轴是底层数组里的数组。而轴的数量——秩,就是数组的维数。

很多时候可以声明 axis。axis=0,表示沿着第 0 轴进行操作,即对每一列进行操作;axis=1,表示沿着第1轴进行操作,即对每一行进行操作。

属性 说明
ndarray.ndim 秩,即轴的数量或维度的数量
ndarray.shape 数组的维度,对于矩阵,n 行 m 列
ndarray.size 数组元素的总个数,相当于 .shape 中 n*m 的值
ndarray.dtype ndarray 对象的元素类型
ndarray.itemsize ndarray 对象中每个元素的大小,以字节为单位
ndarray.flags ndarray 对象的内存信息
ndarray.real ndarray元素的实部
ndarray.imag ndarray 元素的虚部
ndarray.data 包含实际数组元素的缓冲区,由于一般通过数组的索引获取元素,所以通常不需要使用这个属性。
1
2
3
4
5
6
7
8
9
10
11
12
a = np.array([[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25],
[26, 27, 28 ,29, 30],
[31, 32, 33, 34, 35]])

print(type(a)) # <class 'numpy.ndarray'>
print(a.dtype) # int64
print(a.size) # 25
print(a.shape) # (5, 5)
print(a.itemsize) # 8
print(a.ndim) # 2

创建数组

numpy.arange

numpy 包中的使用 arange 函数创建数值范围并返回 ndarray 对象,函数格式如下:

numpy.arange(start, stop, step, dtype)

根据 start 与 stop 指定的范围以及 step 设定的步长,生成一个 ndarray。

参数说明:

参数 描述
start 起始值,默认为0
stop 终止值(不包含)
step 步长,默认为1
dtype 返回ndarray的数据类型,如果没有提供,则会使用输入数据的类型。
1
2
3
4
5
6
7
8
import numpy as np  
x1 = np.arange(5)
print (x1)
# [0 1 2 3 4]

x2 = np.arange(10,20,2,dtype='float')
print (x2)
# [10. 12. 14. 16. 18.]

numpy.empty

numpy.empty 方法用来创建一个指定形状(shape)、数据类型(dtype)且未初始化的数组:

numpy.empty(shape, dtype = float, order = 'C')

参数 描述
shape 数组形状
dtype 数据类型,可选
order 有”C”和”F”两个选项,分别代表,行优先和列优先,在计算机内存中的存储元素的顺序。
1
2
3
4
5
6
7
8
9
import numpy as np
n = np.empty([4,3],dtype= int )
print(n)

# 数组元素为随机值,因为它们未初始化。
# [[7602259 7471215 7536741]
# [6619219 7733362 6488169]
# [4391013 6357100 7536755]
# [7209033 7274598 4456448]]

numpy.zeros

创建指定大小的数组,数组元素以 0 来填充:

numpy.zeros(shape, dtype = float, order = 'C')

参数说明:

参数 描述
shape 数组形状
dtype 数据类型,可选
order ‘C’ 用于 C 的行数组,或者 ‘F’ 用于 FORTRAN 的列数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np  

# 默认为浮点数
x = np.zeros(5)
print(x)
# [0. 0. 0. 0. 0.]

# 设置类型为整数
y = np.zeros((5,), dtype = int)
print(y)
# [0 0 0 0 0]

# 自定义类型,i4即int4
z = np.zeros((2,2), dtype = [('x', 'i4'), ('y', 'i4')])
print(z)
# [[(0, 0) (0, 0)]
# [(0, 0) (0, 0)]]

numpy.ones

创建指定形状的数组,数组元素以 1 来填充:

numpy.ones(shape, dtype = None, order = 'C')

参数说明:

参数 描述
shape 数组形状
dtype 数据类型,可选
order ‘C’ 用于 C 的行数组,或者 ‘F’ 用于 FORTRAN 的列数组
1
2
3
4
5
6
7
8
9
10
11
import numpy as np  
# 默认为浮点数
x = np.ones(5)
print(x)
# [1. 1. 1. 1. 1.]

# 自定义类型
y = np.ones([2,2], dtype = int)
print(y)
# [[1 1]
# [1 1]]

numpy.zeros_like

numpy.zeros_like 用于创建一个与给定数组具有相同形状的数组,数组元素以 0 来填充。

numpy.zeros 和 numpy.zeros_like 都是用于创建一个指定形状的数组,其中所有元素都是 0。

它们之间的区别在于:numpy.zeros 可以直接指定要创建的数组的形状,而 numpy.zeros_like 则是创建一个与给定数组具有相同形状的数组。

numpy.zeros_like(a, dtype=None, order='K', subok=True, shape=None)

参数说明:

参数 描述
a 给定要创建相同形状的数组
dtype 创建的数组的数据类型
order 数组在内存中的存储顺序,可选值为 ‘C’(按行优先)或 ‘F’(按列优先),默认为 ‘K’(保留输入数组的存储顺序)
subok 是否允许返回子类,如果为 True,则返回一个子类对象,否则返回一个与 a 数组具有相同数据类型和存储顺序的数组
shape 创建的数组的形状,如果不指定,则默认为 a 数组的形状。

创建一个与 arr 形状相同的,所有元素都为 0 的数组:

1
2
3
4
5
6
7
8
9
import numpy as np  
# 创建一个 3x3 的二维数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 创建一个与 arr 形状相同的,所有元素都为 0 的数组
zeros_arr = np.zeros_like(arr)
print(zeros_arr)
# [[0 0 0]
# [0 0 0]
# [0 0 0]]

numpy.ones_like

numpy.ones_like 用于创建一个与给定数组具有相同形状的数组,数组元素以 1 来填充。

numpy.ones 和 numpy.ones_like 都是用于创建一个指定形状的数组,其中所有元素都是 1。

它们之间的区别在于:numpy.ones 可以直接指定要创建的数组的形状,而 numpy.ones_like 则是创建一个与给定数组具有相同形状的数组。

numpy.ones_like(a, dtype=None, order='K', subok=True, shape=None)

参数说明:

参数 描述
a 给定要创建相同形状的数组
dtype 创建的数组的数据类型
order 数组在内存中的存储顺序,可选值为 ‘C’(按行优先)或 ‘F’(按列优先),默认为 ‘K’(保留输入数组的存储顺序)
subok 是否允许返回子类,如果为 True,则返回一个子类对象,否则返回一个与 a 数组具有相同数据类型和存储顺序的数组
shape 创建的数组的形状,如果不指定,则默认为 a 数组的形状。

创建一个与 arr 形状相同的,所有元素都为 1 的数组:

1
2
3
4
5
6
7
8
9
import numpy as np  
# 创建一个 3x3 的二维数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 创建一个与 arr 形状相同的,所有元素都为 1 的数组
ones_arr = np.ones_like(arr)
print(ones_arr)
#[[1 1 1]
# [1 1 1]
# [1 1 1]]

numpy.linspace

numpy.linspace 函数用于创建一个一维数组,数组是一个等差数列构成的,格式如下:

np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)

参数说明:

参数 描述
start 序列的起始值
stop 序列的终止值,如果endpointtrue,该值包含于数列中
num 要生成的等步长的样本数量,默认为50
endpoint 该值为 true 时,数列中包含stop值,反之不包含,默认是True。
retstep 如果为 True 时,生成的数组中会显示间距,反之不显示。
dtype ndarray 的数据类型

以下实例用到三个参数,设置起始点为 1 ,终止点为 10,数列个数为 10。

1
2
3
4
5
import numpy as np 
a = np.linspace(1,10,10)

print(a)
## [ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]

切片和索引

ndarray对象的内容可以通过索引或切片来访问和修改,与 Python 中 list 的切片操作一样。

ndarray 数组可以基于 0 - n 的下标进行索引,切片对象可以通过内置的 slice 函数,并设置 start, stop 及 step 参数进行,从原数组中切割出一个新数组。

我们可以通过冒号:分隔切片参数 [start:stop:step] 来进行切片操作:

下面的图表说明了给定的示例切片是如何进行工作的。

numpy数组切片工作原理

切片还可以包括省略号 ,来使选择元组的长度与数组的维度相同。 如果在行位置使用省略号,它将返回包含行中元素的 ndarray。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

a = np.array([[1,2,3],[3,4,5],[4,5,6]])
print (a[...,1]) # 第2列元素
# [2 4 5]
print (a[1,...]) # 第2行元素
# [3 4 5]
print (a[...,1:]) # 第2列及剩下的所有元素
#[[2 3]
# [4 5]
# [5 6]]

数组索引

以下实例获取数组中 (0,0),(1,1) 和 (2,0) 位置处的元素。

1
2
3
4
5
import numpy as np   
my_array = np.array([[1, 2], [3, 4], [5, 6]])
y = my_array[[0,1,2], [0,1,0]]
print (y)
# [1 4 5]

可以借助切片: 与索引数组组合。如下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

a = np.array([[1,2,3],
[4,5,6],
[7,8,9]])
b = a[1:3, 1:3]
c = a[1:3,[1,2]]
d = a[...,1:]

print(b)
# [[5 6]
# [8 9]]

print(c)
# [[5 6]
# [8 9]]

print(d)
#[[2 3]
# [5 6]
# [8 9]]

布尔索引

我们可以通过一个布尔数组来索引目标数组。

布尔索引通过布尔运算(如:比较运算符)来获取符合指定条件的元素的数组。

以下实例获取大于 5 的元素:

1
2
3
4
5
import numpy as np 

x = np.array([[ 0, 1, 2],[ 3, 4, 5],[ 6, 7, 8],[ 9, 10, 11]])
print (x[x > 5])
# [ 6 7 8 9 10 11]

假设每个名字都对应data数组中的一行,而我们想要选出对应于用户”andy”的所有行。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
users = np.array(['andy','jim','andy'])
data = np.random.randint(1,11,size=(3,4))
print(users,data,sep='\n')
# ['andy' 'jim' 'andy']
# [[1 2 3 4]
# [5 7 5 9]
# [5 5 3 8]]
print(data[users == 'andy'])
# [[1 2 3 4]
# [5 5 3 8]]

要选择除”andy”以外的其他值,既可以使用不等于符号(!=),也可以通过~对条件进行否定:

1
2
print(data[~(names == 'andy')])
# [5 7 5 9]

花式索引

花式索引指的是利用整数数组进行索引。

花式索引根据索引数组的值作为目标数组的某个轴的下标来取值。

对于使用一维整型数组作为索引,如果目标是一维数组,那么索引的结果就是对应位置的元素,如果目标是二维数组,那么就是对应下标的行。

花式索引跟切片不一样,它总是将数据复制到新数组中。

一维数组

一维数组只有一个轴 axis = 0,所以一维数组就在 axis = 0 这个轴上取值:

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

x = np.arange(9)
x2 = x[[0, 6]] # 使用花式索引

print(x2)
# [0 6]
print(x2[0])
# 0
print(x2[1])
# 6

二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np 

x = np.arange(32).reshape((8,4))

# 二维数组读取指定下标对应的行
print(x[[4,2,1,7]])
# [[16 17 18 19]
# [ 8 9 10 11]
# [ 4 5 6 7]
# [28 29 30 31]]

# 取所有行的,第1列和第3列
print(x[:,[1,3]])
# [[ 1 3]
# [ 5 7]
# [ 9 11]
# [13 15]
# [17 19]
# [21 23]
# [25 27]
# [29 31]]

# 取第一行第0列,第五行第三列,第二行第一列
print(x[[1,5,2],[0,3,1]])
# [ 4 23 9]

数组操作

修改数组形状

函数 描述
reshape 不改变数据的条件下修改形状
flat 数组元素迭代器
flatten 返回一份数组拷贝,对拷贝所做的修改不会影响原始数组
ravel 返回展开数组

numpy.reshape

numpy.reshape 函数可以在不改变数据的条件下修改形状,格式如下:

numpy.reshape(arr, newshape, order='C')

  • arr:要修改形状的数组
  • newshape:整数或者整数数组,新的形状应当兼容原有形状
  • order:’C’ – 按行,’F’ – 按列,’A’ – 原顺序,’k’ – 元素在内存中的出现顺序。
1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np  
a = np.arange(8)

print (a)
# [0 1 2 3 4 5 6 7]

b = a.reshape(4,2)
print (b)
# [[0 1]
# [2 3]
# [4 5]
# [6 7]]

数组转置和轴对换

转置是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
arr = np.arange(6).reshape((2,3))

print(arr)
# [[0 1 2]
# [3 4 5]]
print(arr.T)
# [[0 3]
# [1 4]
# [2 5]]
print(arr.transpose())
# [[0 3]
# [1 4]
# [2 5]]

在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
arr = np.arange(6).reshape((2,3))

print(arr)
# [[0 1 2]
# [3 4 5]]

print(arr.T)
# [[0 3]
# [1 4]
# [2 5]]

print(np.dot(arr,arr.T))
# [[ 5 14]
# [14 50]]

对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np
arr = np.arange(16).reshape((2, 2, 4))
print(arr)
# [[[ 0 1 2 3]
# [ 4 5 6 7]]
#
# [[ 8 9 10 11]
# [12 13 14 15]]]
print(arr.transpose((1, 0, 2)))
# arr.transpose(1,0,2),这里就是让索引1变换到了索引0的位置,索引0变到了索引1的位置,索引2保持不变
# [[[ 0 1 2 3]
# [ 8 9 10 11]]
#
# [[ 4 5 6 7]
# [12 13 14 15]]]

简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号:

arr.swapaxes(1,2)(实际上等价于arr.swapaxes(2,1)),将轴1和轴2进行互换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np

arr = np.arange(16).reshape((2, 2, 4))
print(arr)
# [[[ 0 1 2 3]
# [ 4 5 6 7]]
#
# [[ 8 9 10 11]
# [12 13 14 15]]]
print(arr.swapaxes(1,2))
# [[[ 0 4]
# [ 1 5]
# [ 2 6]
# [ 3 7]]
#
# [[ 8 12]
# [ 9 13]
# [10 14]
# [11 15]]]

swapaxes也是返回源数据的视图(不会进行任何复制操作)。

利用数组进行数据处理

将条件逻辑表述为数组运算

numpy.where函数是三元表达式x if condition else y的矢量化版本。假设我们有一个布尔数组和两个值数组,我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。列表推导式的写法应该如下所示:

1
2
3
4
5
6
7
8
import numpy as np

xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
result = [(x if c else y) for x,y,c in zip(xarr,yarr,cond)]
print(result)
# [1.1, 2.2, 1.3, 1.4, 2.5]

这有几个问题。第一,它对大数组的处理速度不是很快(因为所有工作都是由纯Python完成的)。第二,无法用于多维数组。若使用np.where,则可以将该功能写得非常简洁:

1
2
3
result = np.where(cond, xarr, yarr)
print(result)
# [1.1 2.2 1.3 1.4 2.5]

np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为’正数’,将所有负值替换为’负数’。若利用np.where,则会非常简单:

1
2
3
4
5
6
7
8
9
10
import numpy as np

arr = np.random.randn(2, 2)
print(arr)
# [[-0.58616216 -1.17916735]
# [ 0.41563713 0.75208178]]

print(np.where(arr > 0, '正数', '负数'))
# [['负数' '负数']
# ['正数' '正数']]

使用np.where,可以将标量和数组结合起来。例如,可用常数2替换arr中所有正的值:

1
2
3
print(np.where(arr>0,2,arr))
# [[-0.58616216 -1.17916735]
# [ 2. 2. ]]

数学和统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

arr= np.random.randint(0,100,size=(3,4))
print(arr)
# [[37 12 72 9]
# [75 5 79 64]
# [16 1 76 71]]

print(arr.mean())
# 43.083333333333336
print(np.mean(arr))
# 43.083333333333336
print(arr.sum())
# 517

mean和sum这类的函数可以接受一个axis选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组:

1
2
3
4
5
print(arr.mean(axis=1))
# [32.5 55.75 41. ]

print(arr.sum(axis=1))
# [130 223 164]

表4-5列出了全部的基本数组统计方法。

img

用于布尔型数组的方法

在上面这些方法中,布尔值会被强制转换为1(True)和0(False)。因此,sum经常被用来对布尔型数组中的True值计数(注意:是计数不是求和!!!)

1
2
3
4
5
6
7
8
9
import numpy as np
arr = np.random.randint(-10,11,size=5)

print(arr)
# [-10 2 9 0 -5]
print(arr>0)
# [False True True False False]
print((arr>0).sum())
# 2

另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:

1
2
3
4
5
6
7
8
import numpy as np

bools = np.array([False, False, True, False])
print(bools.any())
# True

print(bools.all())
# False

这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。

排序

跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序:

1
2
3
4
5
6
7
8
9
import numpy as np
np.random.seed(1)
arr = np.random.randint(-10,11,size=5)
print(arr)
# [-5 1 2 -2 -1]

arr.sort()
print(arr)
# [-5 -2 -1 1 2]

多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
np.random.seed(1)
arr = np.random.randint(-10,11,size=(3,4))
print(arr)
# [[ -5 1 2 -2]
# [ -1 1 -5 5]
# [-10 6 -9 2]]

arr.sort(1)
print(arr)
# [[ -5 -2 1 2]
# [ -5 -1 1 5]
# [-10 -9 2 6]]

唯一化以及其它的集合逻辑

NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能要数np.unique了,它用于找出数组中的唯一值并返回已排序的结果:

1
2
3
4
import numpy as np
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
print(np.unique(names))
# ['Bob' 'Joe' 'Will']

另一个函数np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:

1
2
3
4
import numpy as np
values = np.array([6, 0, 0, 3, 2, 5, 6])
print(np.in1d(values, [2, 3, 6]))
# [ True False False True True False True]

NumPy中的集合函数请参见表4-6。

深拷贝&浅拷贝

概述

  • b = a

    对象b引用对象a,对象b本身没有单独分配内存空间,它指向计算机中存储对象a的内存。a和b相互影响,b发生变化了,b也会发生变化。

  • b = a.view() or b=a[:]

    a 和 b 并不同属一块内存,是两个不同的对象。对 b 的修改会改变 a。

  • b = a.copy()

    ndarray.copy() 函数创建一个副本。 对副本数据进行修改,不会影响到原始数据,它们物理内存不在同一位置。

完全不复制

简单分配不会复制数组对象或其数据。

1
2
3
4
5
6
7
8
9
import numpy as np
a = np.arange(12)
b = a # no new object is created
print(b is a) # a and b are two names for the same ndarray object
# True

b.shape = 3,4 # changes the shape of a
print(a.shape)
# (3, 4)

视图或浅拷贝

不同的数组对象可以共享相同的数据。用view方法创建一个查看相同数据的新数组对象。

view方法可以新建一个新的 ndarray,但是只会 copy 父对象,不会 copy 底层的数据,共用原始引用指向的对象数据。a和b相互影响,b发生变化了,b也会发生变化。

NumPy中,一个ndarray的视图和原ndarray是不同的对象,但是共享ndarray值的内存。这两个对象是相互关联的,这样的好处是不会涉及到数据在内存中的复制,这样可以节省空间,提高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
a = np.arange(12)
c = a.view()
print(c is a, c.base is a)
# False True

c.shape = 2,6 # a's shape doesn't change
print(a.shape)
# (12,)

c[0,4] = 1234 # a's data changes
print(a)
# [ 0 1 2 3 1234 5 6 7 8 9 10 11]

切片数组会返回一个视图:

1
2
3
4
5
6
>>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10 # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
a = np.arange(12).reshape(3,4)
d = a.copy() # a new array object with new data is created

print(d is a)
# False
print(d.base is a)
# False

d[0,0] = 999
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]

有时,如果不再需要原始数组,则应在切片后调用 copy。例如,假设a是一个巨大的中间结果,最终结果b只包含a的一小部分,那么在用切片构造b时应该做一个深拷贝:

1
2
3
a = np.arange(int(1e8))
b = a[:100].copy()
del a # the memory of ``a`` can be released.

Pandas

Pandas 是 Python 的核心数据分析支持库,提供了快速、灵活、明确的数据结构,旨在简单、直观地处理关系型、标记型数据。

Pandas是基于NumPy数组构建的,特别是基于数组的函数和不使用for循环的数据处理。虽然Pandas采用了大量的NumPy编码风格,但二者最大的不同是Pandas是专门为处理表格和混杂数据设计的。而NumPy更适合处理统一的数值数组数据。

数据结构

维数 名称 描述
1 Series 带标签的一维同构数组
2 DataFrame 带标签的,大小可变的,二维异构表格

Series

Pandas Series 类似表格中的一个列(column),类似于一维数组,可以保存任何数据类型。它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series:

pandas.Series( data, index, dtype, name, copy)

  • data:一组数据(ndarray 类型)。
  • index:数据索引标签,如果不指定,默认从 0 开始。
  • dtype:数据类型,默认会自己判断。
  • name:设置名称。
  • copy:拷贝数据,默认为 False。
1
2
3
4
5
6
7
8
9
import pandas as pd
my_data = pd.Series([2021, 2023, 2025])

print(my_data)
# 输出结果
# 0 2021
# 1 2023
# 2 2025
# dtype: int64

Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0到N-1(N为数据的长度)的整数型索引

你可以通过Series 的values和index属性获取其数组表示形式和索引对象:

1
2
3
4
5
6
7
import pandas as pd
my_data = pd.Series([2021, 2023, 2025])

print(my_data.values)
# [2021 2023 2025]
print(my_data.index)
# RangeIndex(start=0, stop=3, step=1)

我们可以指定索引值,如下实例:

1
2
3
4
5
6
7
8
9
import pandas as pd
obj1 = pd.Series(["Google", "Runoob", "Wiki"], index=["x","y","z"])
print(obj1)
# x Google
# y Runoob
# z Wiki
# dtype: object
print(obj1.index)
# Index(['x', 'y', 'z'], dtype='object')

与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值:

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
obj1 = pd.Series(["Google", "Runoob", "Wiki"], index=["x","y","z"])
print(obj1['x'])
# Google

obj1["y"] = "Baidu"
print(obj1[['y','z','x']])
# y Baidu
# z Wiki
# x Google
# dtype: object

Series的索引可以通过赋值的方式就地修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
obj = pd.Series([4,7,-5,3])
print(obj)
# 0 4
# 1 7
# 2 -5
# 3 3
dtype: int64
obj.index=['a','c','d','f']
print(obj)
# a 4
# c 7
# d -5
# f 3
# dtype: int64

我们也可以使用 key/value 对象,类似字典来创建 Series:

1
2
3
4
5
6
7
8
9
10
import pandas as pd
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
obj3 = pd.Series(sdata)
print(obj3)

# Ohio 35000
# Oregon 16000
# Texas 71000
# Utah 5000
# dtype: int64

如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序:

1
2
3
4
5
6
7
8
9
10
11
12
import pandas as pd

sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = pd.Series(sdata, index=states)
print(obj4)

# California NaN
# Ohio 35000.0
# Oregon 16000.0
# Texas 71000.0
# dtype: float64

在这个例子中,sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上,但由于”California”所对应的sdata值找不到,所以其结果就为NaN(即“非数字”(not a number),在pandas中,它用于表示缺失或NA值)。因为‘Utah’不在states中,它被从结果中除去。

pandas的isnullnotnull函数可用于检测缺失数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = pd.Series(sdata, index=states)
print(pd.isnull(obj4))
# California True
# Ohio False
# Oregon False
# Texas False
# dtype: bool

print(pd.notnull(obj4))
# California False
# Ohio True
# Oregon True
# Texas True
# dtype: bool

Series也有类似的实例方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd

sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = pd.Series(sdata, index=states)
print(obj4.isnull())
# California True
# Ohio False
# Oregon False
# Texas False
# dtype: bool
print(obj4.notnull())
# California False
# Ohio True
# Oregon True
# Texas True
# dtype: bool

Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd

sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = pd.Series(sdata, index=states)
obj4.name = 'population'
obj4.index.name = 'state'

print(obj4)
# state
# California NaN
# Ohio 35000.0
# Oregon 16000.0
# Texas 71000.0
# Name: population, dtype: float64

DataFrame

DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)。DataFrame 既有行索引也有列索引,它可以被看做由 Series 组成的字典(共同用一个索引)。DataFrame中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。

img

img

pandas.DataFrame( data, index, columns, dtype, copy)

  • data:一组数据(ndarray、series, map, lists, dict 等类型)。
  • index:索引值,或者可以称为行标签。
  • columns:列标签,默认为 RangeIndex (0, 1, 2, …, n) 。
  • dtype:数据类型。
  • copy:拷贝数据,默认为 False。
1
2
3
4
5
6
7
8
9
10
import pandas as pd
data = [['Google',10],['Baidu',12],['Wiki',13]]
df = pd.DataFrame(data,columns=['Site','Age'])
print(df)

# 输出结果
# Site Age
# 0 Google 10
# 1 Baidu 12
# 2 Wiki 13

建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典:

1
2
3
4
5
6
7
8
9
10
import pandas as pd
data = {'Site':['Google', 'Baidu', 'Wiki'], 'Age':[10, 12, 13]}
df = pd.DataFrame(data)
print (df)

# 输出结果
# Site Age
# 0 Google 10
# 1 Baidu 12
# 2 Wiki 13

如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列:

1
2
3
4
5
6
7
8
import pandas as pd
data = {'Site':['Google', 'Runoob', 'Wiki'], 'Age':[10, 12, 13]}
df = pd.DataFrame(data, columns=['Age'])
print (df)
# Age
# 0 10
# 1 12
# 2 13

如果传入的列在数据中找不到,就会在结果中产生缺失值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=[1, 2, 3, 4, 5, 6])

print(frame2)
# year state pop debt
# 1 2000 Ohio 1.5 NaN
# 2 2001 Ohio 1.7 NaN
# 3 2002 Ohio 3.6 NaN
# 4 2001 Nevada 2.4 NaN
# 5 2002 Nevada 2.9 NaN
# 6 2003 Nevada 3.2 NaN

列可以通过赋值的方式进行修改。例如,我们可以给那个空的”debt”列赋上一个标量值或一组值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
import numpy as np

data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=[1, 2, 3, 4, 5, 6])

frame2['debt'] = np.arange(6)
print(frame2)
# year state pop debt
# 1 2000 Ohio 1.5 0
# 2 2001 Ohio 1.7 1
# 3 2002 Ohio 3.6 2
# 4 2001 Nevada 2.4 3
# 5 2002 Nevada 2.9 4
# 6 2003 Nevada 3.2 5

将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=[1, 2, 3, 4, 5, 6])
val = pd.Series([-1.2, -1.5, -1.7], index=[1, 4, 5])
frame2['debt'] = val

print(frame2)
# year state pop debt
# 1 2000 Ohio 1.5 -1.2
# 2 2001 Ohio 1.7 NaN
# 3 2002 Ohio 3.6 NaN
# 4 2001 Nevada 2.4 -1.5
# 5 2002 Nevada 2.9 -1.7
# 6 2003 Nevada 3.2 NaN

del方法可以用来删除列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=[1, 2, 3, 4, 5, 6])
print(frame2)
# year state pop debt
# 1 2000 Ohio 1.5 NaN
# 2 2001 Ohio 1.7 NaN
# 3 2002 Ohio 3.6 NaN
# 4 2001 Nevada 2.4 NaN
# 5 2002 Nevada 2.9 NaN
# 6 2003 Nevada 3.2 NaN
del frame2['debt']
print(frame2)
# year state pop
# 1 2000 Ohio 1.5
# 2 2001 Ohio 1.7
# 3 2002 Ohio 3.6
# 4 2001 Nevada 2.4
# 5 2002 Nevada 2.9
# 6 2003 Nevada 3.2

Pandas 可以使用 loc 属性返回指定行的数据,如果没有设置索引,第一行索引为 0,第二行索引为 1,以此类推:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
data = {
"calories": [420, 380, 390],
"duration": [50, 40, 45]
}
# 数据载入到 DataFrame 对象
df = pd.DataFrame(data)

# 返回第一行
print(df.loc[0])
# 返回第二行
print(df.loc[1])

# 输出结果如下:
# calories 420
# duration 50
# Name: 0, dtype: int64
# calories 380
# duration 40
# Name: 1, dtype: int64

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
df.head(5) #查看前5行
df.tail(3) #查看后3行
df.values #查看数值
df.shape #查看行数、列数
df.fillna(0) #将空值填充0
df.replace(1, -1) #将1替换成-1
df.isnull() #查找数据中出现的空值
df.notnull() #非空值
df.dropna() #删除空值
df.unique() #查看唯一值
df.reset_index() #修改、删除,原有索引
df.columns #查看数据的列名
df.index #查看索引
df.sort_index() #索引排序
df.sort_values() #值排序
pd.merge(数据1,数据1) #合并
pd.concat([数据1,数据2]) #合并,与merge的区别,自查
pd.pivot_table( 数据 ) #用df做数据透视表(类似于Excel的数透)

索引对象

Pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:

1
2
3
4
5
6
7
8
9
import pandas as pd
obj = pd.Series(range(3),index=['a', 'b', 'c'])
index = obj.index

print(index)
# Index(['a', 'b', 'c'], dtype='object')

print(index[:3])
# Index(['a', 'b'], dtype='object')

Index对象是不可变的,因此用户不能对其进行修改:

1
index[1] = 'd'  # TypeError

除了类似于数组,Index的功能也类似一个固定大小的集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data,index=['a','b','c','d','s','e'])
print(frame)
# state year pop
# a Ohio 2000 1.5
# b Ohio 2001 1.7
# c Ohio 2002 3.6
# d Nevada 2001 2.4
# s Nevada 2002 2.9
# e Nevada 2003 3.2
print(frame.index)
# Index(['a', 'b', 'c', 'd', 's', 'e'], dtype='object')

与python的集合不同,pandas的Index可以包含重复的标签:

1
2
3
4
import pandas as pd
dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar'])
print(dup_labels)
# Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-2列出了这些函数。

img

基本功能

重新索引

pandas对象的一个重要方法是reindex,其作用是创建一个新对象,它的数据符合新的索引。

1
2
3
4
5
6
7
8
import pandas as pd
obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
print(obj)
# d 4.5
# b 7.2
# a -5.3
# c 3.6
# dtype: float64

用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值:

1
2
3
4
5
6
7
8
obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
print(obj2)
# a -5.3
# b 7.2
# c 3.6
# d 4.5
# e NaN
# dtype: float64

对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method选项即可达到此目的,例如,使用ffill可以实现前向值填充:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
print(obj3)
# 0 blue
# 2 purple
# 4 yellow
# dtype: object
print(obj3.reindex(range(6),method='ffill'))
# 0 blue
# 1 blue
# 2 purple
# 3 purple
# 4 yellow
# 5 yellow
# dtype: object

借助DataFrame,reindex可以修改(行)索引和列。只传递一个序列时,会重新索引结果的行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np
frame = pd.DataFrame(np.arange(9).reshape((3, 3)),index=['b','a','c'],columns=['Ohio', 'Texas', 'California'])
print(frame)
# Ohio Texas California
# b 0 1 2
# a 3 4 5
# c 6 7 8

frame2 = frame.reindex(['a', 'b', 'c', 'd'])
print(frame2)
# Ohio Texas California
# a 0.0 1.0 2.0
# b 3.0 4.0 5.0
# c 6.0 7.0 8.0
# d NaN NaN NaN

列可以用columns关键字重新索引:

1
2
3
4
5
6
7
8
9
import pandas as pd
import numpy as np
frame = pd.DataFrame(np.arange(9).reshape((3, 3)),index=['b','a','c'],columns=['Ohio', 'Texas', 'California'])
states = ['Texas', 'Utah', 'California']
print(frame.reindex(columns=states))
# Texas Utah California
# b 1 NaN 2
# a 4 NaN 5
# c 7 NaN 8

丢弃指定轴上的项

丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以drop方法返回的是一个在指定轴上删除了指定值的新对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
import pandas as pd
obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
print(obj)
# a 0.0
# b 1.0
# c 2.0
# d 3.0
# e 4.0
# dtype: float64

print(obj.drop('c'))
# a 0.0
# b 1.0
# d 3.0
# e 4.0
# dtype: float64

print(obj.drop(['d','e']))
# a 0.0
# b 1.0
# c 2.0
# dtype: float64

对于DataFrame,可以删除任意轴上的索引值。

1
2
3
4
5
6
7
8
9
import pandas as pd
import numpy as np
data = pd.DataFrame(np.arange(16).reshape(4,4),index=['Ohio', 'Colorado', 'Utah', 'New York'],columns=['one', 'two', 'three', 'four'])
print(data)
# one two three four
# Ohio 0 1 2 3
# Colorado 4 5 6 7
# Utah 8 9 10 11
# New York 12 13 14 15

用标签序列调用drop会从行标签(axis=0)删除值:

1
2
3
4
print(data.drop(['Colorado', 'Ohio']))
# one two three four
# Utah 8 9 10 11
# New York 12 13 14 15

通过传递axis=1或axis=’columns’可以删除列的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
print(data.drop('two', axis=1))
# one three four
# Ohio 0 2 3
# Colorado 4 6 7
# Utah 8 10 11
# New York 12 14 15

print(data.drop(['two','four'], axis='columns'))
# one three
# Ohio 0 2
# Colorado 4 6
# Utah 8 10
# New York 12 14

许多函数,如drop,会修改Series或DataFrame的大小或形状,可以就地修改对象,不会返回新的对象:

1
2
3
4
5
6
7
8
9
10
import pandas as pd
import numpy as np
obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
obj.drop('c', inplace=True)
print(obj)
# a 0.0
# b 1.0
# d 3.0
# e 4.0
# dtype: float64

小心使用inplace,它会销毁所有被删除的数据。

索引、选取和过滤

Series索引(obj[…])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是整数。下面是几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pandas as pd
import numpy as np
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
print(obj)
# a 0.0
# b 1.0
# c 2.0
# d 3.0
# dtype: float64
print(obj['b'])
# 1.0
print(obj[1])
# 1.0
print(obj[2:4])
# c 2.0
# d 3.0
# dtype: float64
print(obj[['b', 'a', 'd']])
# b 1.0
# a 0.0
# d 3.0
# dtype: float64
print(obj[[1, 3]])
# b 1.0
# d 3.0
# dtype: float64
print(obj[obj < 2])
# a 0.0
# b 1.0
# dtype: float64

利用标签的切片运算与普通的Python切片运算不同,其末端是包含的

1
2
3
4
5
6
7
8
import pandas as pd
import numpy as np
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])

print(obj['b':'c'])
# b 1.0
# c 2.0
# dtype: float64

用切片可以对Series的相应部分进行设置:

1
2
3
4
5
6
7
8
9
10
import pandas as pd
import numpy as np
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
obj['b':'c'] = 5
print(obj)
# a 0.0
# b 5.0
# c 5.0
# d 3.0
# dtype: float64

用一个值或序列对DataFrame进行索引其实就是获取一个或多个列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import numpy as np
data = pd.DataFrame(np.arange(16).reshape((4, 4)),index=['Ohio', 'Colorado', 'Utah', 'New York'],columns=['one', 'two', 'three', 'four'])
print(data)
# one two three four
# Ohio 0 1 2 3
# Colorado 4 5 6 7
# Utah 8 9 10 11
# New York 12 13 14 15

print(data['two'])
# Ohio 1
# Colorado 5
# Utah 9
# New York 13
# Name: two, dtype: int32

print(data[['three', 'one']])
# three one
# Ohio 2 0
# Colorado 6 4
# Utah 10 8
# New York 14 12

这种索引方式有几个特殊的情况。首先通过切片或布尔型数组选取数据:

1
2
3
4
5
6
7
8
9
10
print(data[:2])
# one two three four
# Ohio 0 1 2 3
# Colorado 4 5 6 7

print(data[data['three'] > 5])
# one two three four
# Colorado 4 5 6 7
# Utah 8 9 10 11
# New York 12 13 14 15

另一种用法是通过布尔型DataFrame(比如下面这个由标量比较运算得出的)进行索引,这使得DataFrame的语法与NumPy二维数组的语法很像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print(data<5)
# one two three four
# Ohio True True True True
# Colorado True False False False
# Utah False False False False
# New York False False False False

data[data<5] = 0
print(data)
# one two three four
# Ohio 0 0 0 0
# Colorado 0 5 6 7
# Utah 8 9 10 11
# New York 12 13 14 15

query()

DataFrame对象有一个query() 允许使用表达式进行选择的方法。完全类似numpy的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
In [232]: df = pd.DataFrame(np.random.randint(10, size=(10, 3)), columns=list('abc'))

In [233]: df
Out[233]:
a b c
0 7 8 9
1 1 0 7
2 2 7 2
3 6 2 2
4 2 6 3
5 3 8 2
6 1 7 2
7 5 1 5
8 9 8 0
9 1 5 0

In [234]: df.query('(a < b) & (b < c)')
Out[234]:
a b c
0 7 8 9

In [235]: df[(df.a < df.b) & (df.b < df.c)]
Out[235]:
a b c
0 7 8 9

通过删除括号略微更好(通过绑定使比较运算符绑定比&和更紧|)。

1
2
3
4
In [236]: df.query('a < b & b < c')
Out[236]:
a b c
0 7 8 9

使用英语而不是符号:

1
2
3
4
In [237]: df.query('a < b and b < c')
Out[237]:
a b c
0 7 8 9

非常接近你如何在纸上写它:

1
2
3
4
In [238]: df.query('a < b < c')
Out[238]:
a b c
0 7 8 9

query()还支持Python in和 比较运算符的特殊用法,为调用或的方法提供了简洁的语法 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# get all rows where columns "a" and "b" have overlapping values
In [239]: df = pd.DataFrame({'a': list('aabbccddeeff'), 'b': list('aaaabbbbcccc'),
.....: 'c': np.random.randint(5, size=12),
.....: 'd': np.random.randint(9, size=12)})
.....:

In [240]: df
Out[240]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
3 b a 2 1
4 c b 3 6
5 c b 0 2
6 d b 3 3
7 d b 2 1
8 e c 4 3
9 e c 2 0
10 f c 0 6
11 f c 1 2

In [241]: df.query('a in b')
Out[241]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
3 b a 2 1
4 c b 3 6
5 c b 0 2

# How you'd do it in pure Python
In [242]: df[df.a.isin(df.b)]
Out[242]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
3 b a 2 1
4 c b 3 6
5 c b 0 2

In [243]: df.query('a not in b')
Out[243]:
a b c d
6 d b 3 3
7 d b 2 1
8 e c 4 3
9 e c 2 0
10 f c 0 6
11 f c 1 2

# pure Python
In [244]: df[~df.a.isin(df.b)]
Out[244]:
a b c d
6 d b 3 3
7 d b 2 1
8 e c 4 3
9 e c 2 0
10 f c 0 6
11 f c 1 2

您可以将此与其他表达式结合使用,以获得非常简洁的查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# rows where cols a and b have overlapping values
# and col c's values are less than col d's
In [245]: df.query('a in b and c < d')
Out[245]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
4 c b 3 6
5 c b 0 2

# pure Python
In [246]: df[df.b.isin(df.a) & (df.c < df.d)]
Out[246]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
4 c b 3 6
5 c b 0 2
10 f c 0 6
11 f c 1 2

==运算符与list对象的特殊用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
In [247]: df.query('b == ["a", "b", "c"]')
Out[247]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
3 b a 2 1
4 c b 3 6
5 c b 0 2
6 d b 3 3
7 d b 2 1
8 e c 4 3
9 e c 2 0
10 f c 0 6
11 f c 1 2

# pure Python
In [248]: df[df.b.isin(["a", "b", "c"])]
Out[248]:
a b c d
0 a a 2 6
1 a a 4 7
2 b a 1 6
3 b a 2 1
4 c b 3 6
5 c b 0 2
6 d b 3 3
7 d b 2 1
8 e c 4 3
9 e c 2 0
10 f c 0 6
11 f c 1 2

In [249]: df.query('c == [1, 2]')
Out[249]:
a b c d
0 a a 2 6
2 b a 1 6
3 b a 2 1
7 d b 2 1
9 e c 2 0
11 f c 1 2

In [250]: df.query('c != [1, 2]')
Out[250]:
a b c d
1 a a 4 7
4 c b 3 6
5 c b 0 2
6 d b 3 3
8 e c 4 3
10 f c 0 6

# using in/not in
In [251]: df.query('[1, 2] in c')
Out[251]:
a b c d
0 a a 2 6
2 b a 1 6
3 b a 2 1
7 d b 2 1
9 e c 2 0
11 f c 1 2

In [252]: df.query('[1, 2] not in c')
Out[252]:
a b c d
1 a a 4 7
4 c b 3 6
5 c b 0 2
6 d b 3 3
8 e c 4 3
10 f c 0 6

# pure Python
In [253]: df[df.c.isin([1, 2])]
Out[253]:
a b c d
0 a a 2 6
2 b a 1 6
3 b a 2 1
7 d b 2 1
9 e c 2 0
11 f c 1 2

用loc和iloc进行选取

对于DataFrame的行的标签索引,引入了特殊的标签运算符loc和iloc。它们可以让你用类似NumPy的标记,使用轴标签(loc)或整数索引(iloc),从DataFrame选择行和列的子集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import numpy as np
data = pd.DataFrame(np.arange(16).reshape((4, 4)),index=['Ohio', 'Colorado', 'Utah', 'New York'],columns=['one', 'two', 'three', 'four'])
print(data)
# one two three four
# Ohio 0 1 2 3
# Colorado 4 5 6 7
# Utah 8 9 10 11
# New York 12 13 14 15

print(data.loc['Colorado', ['two', 'three']])
# two 5
# three 6
# Name: Colorado, dtype: int32#

用iloc和整数进行选取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
print(data.iloc[2, [3, 0, 1]])
# four 11
# one 8
# two 9
# Name: Utah, dtype: int32
print(data.iloc[2])
# one 8
# two 9
# three 10
# four 11
# Name: Utah, dtype: int64
print(data.iloc[[1, 2], [3, 0, 1]])
# four one two
# Colorado 7 0 5
# Utah 11 8 9

这两个索引函数也适用于一个标签或多个标签的切片:

1
2
3
4
5
6
7
8
9
10
print(data.loc[:'Utah', 'two'])
# Ohio 1
# Colorado 5
# Utah 9
# Name: two, dtype: int32
print(data.iloc[:, :3][data.three > 5])
# one two three
# Colorado 0 5 6
# Utah 8 9 10
# New York 12 13 14

表5-4 DataFrame的索引选项

整数索引

处理整数索引的pandas对象常常难住新手,因为它与Python内置的列表和元组的索引语法不同。

1
2
3
4
5
6
7
8
9
10
import pandas as pd
import numpy as np
ser = pd.Series(np.arange(3.))
print(ser)
# 0 0.0
# 1 1.0
# 2 2.0
# dtype: float64
print(ser[-1])
# ValueError: -1 is not in range

对于非整数索引,不会产生歧义:

1
2
3
4
5
import pandas as pd
import numpy as np
ser2 = pd.Series(np.arange(3.), index=['a', 'b', 'c'])
print(ser2[-1])
# 2.0

为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准确,请使用loc(标签)或iloc(整数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import numpy as np
ser = pd.Series(np.arange(3.),index=[0,3,4])
print(ser)
# 0 0.0
# 3 1.0
# 4 2.0
# dtype: float64
print(ser[1])
# KeyError: 1
print(ser[:2]) # 数据选取0,1行
# 0 0.0
# 3 1.0
# dtype: float64
print(ser.loc[:3]) # 数据选取使用标签3和之前的行
# 0 0.0
# 3 1.0
# dtype: float64
print(ser.iloc[:3]) # 数据选取前3行
# 0 0.0
# 3 1.0
# 4 2.0
# dtype: float64

算术运算和数据对齐

pandas最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
print(s1,s2)
# a 7.3
# c -2.5
# d 3.4
# e 1.5
# dtype: float64
# a -2.1
# c 3.6
# e -1.5
# f 4.0
# g 3.1
# dtype: float64
print(s1+s2)
# a 5.2
# c 1.1
# d NaN
# e 0.0
# f NaN
# g NaN
# dtype: float64

自动的数据对齐操作在不重叠的索引处引入了NA值。缺失值会在算术运算过程中传播。

对于DataFrame,对齐操作会同时发生在行和列上,把它们相加后将会返回一个新的DataFrame,其索引和列为原来那两个DataFrame的并集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(df1)
# b c d
# Ohio 0.0 1.0 2.0
# Texas 3.0 4.0 5.0
# Colorado 6.0 7.0 8.0
print(df2)
# b d e
# Utah 0.0 1.0 2.0
# Ohio 3.0 4.0 5.0
# Texas 6.0 7.0 8.0
# Oregon 9.0 10.0 11.0
print(df1+df2)
# b c d e
# Colorado NaN NaN NaN NaN
# Ohio 3.0 NaN 6.0 NaN
# Oregon NaN NaN NaN NaN
# Texas 9.0 NaN 12.0 NaN
# Utah NaN NaN NaN NaN

因为’c’和’e’列均不在两个DataFrame对象中,在结果中以缺省值呈现。行也是同样。

如果DataFrame对象相加,没有共用的列或行标签,结果都会是空:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np
df1 = pd.DataFrame({'A': [1, 2]})
df2 = pd.DataFrame({'B': [3, 4]})
print(df1)
# A
# 0 1
# 1 2
print(df2)
# B
# 0 3
# 1 4
print(df1-df2)
# A B
# 0 NaN NaN
# 1 NaN NaN

在算术方法中填充值

在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如0):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), columns=list('abcd'))
df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)), columns=list('abcde'))
print(df1)
# a b c d
# 0 0.0 1.0 2.0 3.0
# 1 4.0 5.0 6.0 7.0
# 2 8.0 9.0 10.0 11.0#
print(df2)
# a b c d e
# 0 0.0 1.0 2.0 3.0 4.0
# 1 5.0 NaN 7.0 8.0 9.0
# 2 10.0 11.0 12.0 13.0 14.0
# 3 15.0 16.0 17.0 18.0 19.0

将它们相加时,没有重叠的位置就会产生NA值:

1
2
3
4
5
6
print(df1+df2)
# a b c d e
# 0 0.0 2.0 4.0 6.0 NaN
# 1 9.0 11.0 13.0 15.0 NaN
# 2 18.0 20.0 22.0 24.0 NaN
# 3 NaN NaN NaN NaN NaN

使用df1的add方法,传入df2以及一个fill_value参数:

1
2
3
4
5
6
print(df1.add(df2, fill_value=0))
# a b c d e
# 0 0.0 2.0 4.0 6.0 4.0
# 1 9.0 11.0 13.0 15.0 9.0
# 2 18.0 20.0 22.0 24.0 14.0
# 3 15.0 16.0 17.0 18.0 19.0

表5-5列出了Series和DataFrame的算术方法。它们每个都有一个副本,以字母r开头,它会翻转参数。因此这两个语句是等价的:

1
2
3
4
5
6
7
8
9
10
11
print(1 / df1)
# a b c d
# 0 inf 1.000000 0.500000 0.333333
# 1 0.250 0.200000 0.166667 0.142857
# 2 0.125 0.111111 0.100000 0.090909

print(df1.rdiv(1))
# a b c d
# 0 inf 1.000000 0.500000 0.333333
# 1 0.250 0.200000 0.166667 0.142857
# 2 0.125 0.111111 0.100000 0.090909

表5-5 灵活的算术方法

在对Series或DataFrame重新索引时,也可以指定一个填充值:

1
2
3
4
5
print(df1.reindex(columns=df2.columns, fill_value=0))
# a b c d e
# 0 0.0 1.0 2.0 3.0 0
# 1 4.0 5.0 6.0 7.0 0
# 2 8.0 9.0 10.0 11.0 0

DataFrame和Series之间的运算

跟不同维度的NumPy数组一样,DataFrame和Series之间算术运算也是有明确规定的。

1
2
3
4
5
6
7
8
9
10
11
arr = np.arange(12.).reshape((3, 4))
print(arr)
# [[ 0. 1. 2. 3.]
# [ 4. 5. 6. 7.]
# [ 8. 9. 10. 11.]]
print(arr[0])
# [0. 1. 2. 3.]
print(arr-arr[0])
# [[0. 0. 0. 0.]
# [4. 4. 4. 4.]
# [8. 8. 8. 8.]]

当我们从arr减去arr[0],每一行都会执行这个操作。这就叫做广播(broadcasting)。DataFrame和Series之间的运算差不多也是如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np
frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),columns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]
print(frame)
# b d e
# Utah 0.0 1.0 2.0
# Ohio 3.0 4.0 5.0
# Texas 6.0 7.0 8.0
# Oregon 9.0 10.0 11.0
print(series)
# b 0.0
# d 1.0
# e 2.0
# Name: Utah, dtype: float64

默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播:

1
2
3
4
5
6
print(frame - series)
# b d e
# Utah 0.0 0.0 0.0
# Ohio 3.0 3.0 3.0
# Texas 6.0 6.0 6.0
# Oregon 9.0 9.0 9.0

如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集:

1
2
3
4
5
6
7
series2 = pd.Series(range(3), index=['b', 'e', 'f'])
print(frame + series2)
# b d e f
# Utah 0.0 NaN 3.0 NaN
# Ohio 3.0 NaN 6.0 NaN
# Texas 6.0 NaN 9.0 NaN
# Oregon 9.0 NaN 12.0 NaN

如果你希望匹配行且在列上广播,则必须使用算术运算方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),columns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series3 = frame['d']
print(frame)
# b d e
# Utah 0.0 1.0 2.0
# Ohio 3.0 4.0 5.0
# Texas 6.0 7.0 8.0
# Oregon 9.0 10.0 11.0
print(series3)
# Utah 1.0
# Ohio 4.0
# Texas 7.0
# Oregon 10.0
# Name: d, dtype: float64
print(frame.sub(series3,axis='index'))
# b d e
# Utah -1.0 0.0 1.0
# Ohio -1.0 0.0 1.0
# Texas -1.0 0.0 1.0
# Oregon -1.0 0.0 1.0

传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配DataFrame的行索引(axis=’index’ or axis=0)并进行广播。

函数应用和映射

NumPy的ufuncs(元素级数组方法)也可用于操作pandas对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np
frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(frame)
# b d e
# Utah -1.959410 -0.291247 -0.618529
# Ohio 1.166250 0.450708 -0.899463
# Texas -0.056786 -0.205335 -1.748267
# Oregon -0.439196 2.333779 0.424394
print(np.abs(frame))
# b d e
# Utah 1.959410 0.291247 0.618529
# Ohio 1.166250 0.450708 0.899463
# Texas 0.056786 0.205335 1.748267
# Oregon 0.439196 2.333779 0.424394

另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能:

1
2
3
4
5
6
f = lambda x: x.max() - x.min()
print(frame.apply(f))
# b 1.802165
# d 1.684034
# e 2.689627
# dtype: float64

这里的函数f,计算了一个Series的最大值和最小值的差,在frame的每列都执行了一次。结果是一个Series,使用frame的列作为索引。

如果传递axis=’columns’到apply,这个函数会在每行执行:

1
2
3
4
5
6
print(frame.apply(f, axis='columns'))
# Utah 0.998382
# Ohio 2.521511
# Texas 0.676115
# Oregon 2.542656
# dtype: float64

许多最为常见的数组统计功能都被实现成DataFrame的方法(如sum和mean),因此无需使用apply方法。

元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可:

1
2
3
4
5
6
7
format = lambda x: '%.2f' % x
print(frame.applymap(format))
# b d e
# Utah -0.20 0.48 -0.52
# Ohio -0.56 1.97 1.39
# Texas 0.09 0.28 0.77
# Oregon 1.25 1.01 -1.30

之所以叫做applymap,是因为Series有一个用于应用元素级函数的map方法:

1
2
3
4
5
6
7
format = lambda x: '%.2f' % x
print(frame['e'].map(format))
# Utah -0.52
# Ohio 1.39
# Texas 0.77
# Oregon -1.30
# Name: e, dtype: object

排序和排名

sort_index()

根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序的新对象:

1
2
3
4
5
6
7
8
import pandas as pd
obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
print(obj.sort_index())
# a 1
# b 2
# c 3
# d 0
# dtype: int64

对于DataFrame,则可以根据任意一个轴上的索引进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np
frame = pd.DataFrame(np.arange(8).reshape((2, 4)),index=['three', 'one'],columns=['d', 'a', 'b', 'c'])
print(frame)
# d a b c
# three 0 1 2 3
# one 4 5 6 7
print(frame.sort_index())
# d a b c
# one 4 5 6 7
# three 0 1 2 3
print(frame.sort_index(axis=1))
# a b c d
# three 1 2 3 0
# one 5 6 7 4

ascending

数据默认是按升序排序的,但也可以降序排序:

1
2
3
4
frame.sort_index(axis=1, ascending=False)
# d c b a
# three 0 3 2 1
# one 4 7 6 5

sort_values()

若要按值对Series进行排序,可使用其sort_values方法:

1
2
3
4
5
6
7
obj = pd.Series([4, 7, -3, 2])
print(obj.sort_values())
# 2 -3
# 3 2
# 0 4
# 1 7
# dtype: int64

在排序时,任何缺失值默认都会被放到Series的末尾:

1
2
3
4
5
6
7
8
9
obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
print(obj.sort_values())
# 4 -3.0
# 5 2.0
# 0 4.0
# 2 7.0
# 1 NaN
# 3 NaN
# dtype: float64

当排序一个DataFrame时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
print(frame)
# a b
# 0 0 4
# 1 1 7
# 2 0 -3
# 3 1 2
print(frame.sort_values(by='b'))
# a b
# 2 0 -3
# 3 1 2
# 0 0 4
# 1 1 7

要根据多个列进行排序,传入名称的列表即可:

1
2
3
4
5
6
frame.sort_values(by=['a', 'b'])
# a b
# 2 0 -3
# 0 0 4
# 3 1 2
# 1 1 7

接下来介绍Series和DataFrame的rank方法。默认情况下,rank是通过“为各组分配一个平均排名”的方式破坏平级关系的:

1
2
3
4
5
6
7
8
9
10
obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj.rank())
# 0 6.5
# 1 1.0
# 2 6.5
# 3 4.5
# 4 3.0
# 5 2.0
# 6 4.5
# dtype: float64

如果两个数值相同,排名是它们的均值,例如数值7在Series中排6、7,平均值为6.5。

也可以根据值在原数据中出现的顺序给出排名:

1
2
3
4
5
6
7
8
9
print(obj.rank(method='first'))
# 0 6.0
# 1 1.0
# 2 7.0
# 3 4.0
# 4 3.0
# 5 2.0
# 6 5.0
# dtype: float64

这里,条目0和2没有使用平均排名6.5,它们被设成了6和7,因为数据中标签0位于标签2的前面。

你也可以按降序进行排名:

1
2
3
4
5
6
7
8
9
print(obj.rank(ascending=False, method='max'))
# 0 2.0
# 1 7.0
# 2 2.0
# 3 4.0
# 4 5.0
# 5 6.0
# 6 4.0
# dtype: float64

表5-6 排名时用于破坏平级关系的方法

带有重复标签的轴索引

1
2
3
4
5
6
7
8
obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
print(obj)
# a 0
# a 1
# b 2
# b 3
# c 4
# dtype: int64

索引的is_unique属性可以告诉你它的值是否是唯一的:

1
2
print(obj.index.is_unique)
# False

对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个Series;而对应单个值的,则返回一个标量值:

1
2
3
4
5
6
print(obj['a'])
# a 0
# a 1
# dtype: int64
print(obj['c'])
# 4

这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。

对DataFrame的行进行索引时也是如此:

1
2
3
4
5
6
7
8
9
10
11
df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
print(df)
# 0 1 2
# a 0.274992 0.228913 1.352917
# a 0.886429 -2.001637 -0.371843
# b 1.669025 -0.438570 -0.539741
# b 0.476985 3.248944 -1.021228
print(df.loc['b'])
# 0 1 2
# b 1.669025 -0.438570 -0.539741
# b 0.476985 3.248944 -1.021228

汇总和计算描述统计

pandas对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从Series中提取单个值(如sum或mean)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。

1
2
3
4
5
6
7
8
9
import numpy as np
import pandas as pd
df = pd.DataFrame([[1.4,np.nan],[7.1,-4.5],[np.nan,np.nan],[0.75,-1.3]],index=['a','b','c','d'],columns=['one','two'])
print(df)
# one two
# a 1.40 NaN
# b 7.10 -4.5
# c NaN NaN
# d 0.75 -1.3

sum()

调用DataFrame的sum方法将会返回一个含有列的和的Series:

1
2
3
4
print(df.sum())
# one 9.25
# two -5.80
# dtype: float64

skipna

NA值会自动被排除,除非整个切片(这里指的是行或列)都是NA。通过skipna选项可以禁用该功能:

1
2
3
4
5
6
7
8
9
10
11
12
print(df.mean(axis='columns'))
# a 1.400
# b 1.300
# c NaN
# d -0.275
# dtype: float64
print(df.mean(axis='columns', skipna=False))
# a NaN
# b 1.300
# c NaN
# d -0.275
# dtype: float64

表5-7列出了这些约简方法的常用选项。

idxmax()

有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最小值或最大值的索引):

1
2
3
4
print(df.idxmax())
# one b
# two d
# dtype: object

cumsum()

另一些方法则是累计型的:

1
2
3
4
5
6
print(df.cumsum())
# one two
# a 1.40 NaN
# b 8.50 -4.5
# c NaN NaN
# d 9.25 -5.8

describe()

还有一种方法,它既不是约简型也不是累计型。describe就是一个例子,它用于一次性产生多个汇总统计:

1
2
3
4
5
6
7
8
9
10
print(df.describe())
# one two
# count 3.000000 2.000000
# mean 3.083333 -2.900000
# std 3.493685 2.262742
# min 0.750000 -4.500000
# 25% 1.075000 -3.700000
# 50% 1.400000 -2.900000
# 75% 4.250000 -2.100000
# max 7.100000 -1.300000

对于非数值型数据,describe会产生另外一种汇总统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
obj = pd.Series(['a', 'a', 'b', 'c'] * 2)
# 0 a
# 1 a
# 2 b
# 3 c
# 4 a
# 5 a
# 6 b
# 7 c
# dtype: object
print(obj.describe())
# count 8
# unique 3
# top a
# freq 4
# dtype: object

unique()

它可以得到Series中的唯一值数组:

1
2
3
4
5
obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
print(obj.unique())
# ['c' 'a' 'd' 'b']
print(obj.nunique())
# 4

返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。

nunique()

计算指定轴中不同元素的数量。

Series.nunique(dropna=True)

1
2
3
4
5
6
7
8
9
10
s = pd.Series([1, 3, 5, 7, 7])
print(s)
# 0 1
# 1 3
# 2 5
# 3 7
# 4 7
# dtype: int64
print(s.nunique())
# 4

DataFrame.nunique(axis=0, dropna=True)

1
2
3
4
5
6
7
8
9
10
df = pd.DataFrame({'A': [4, 5, 6], 'B': [4, 1, 1]})
print(df)
# A B
# 0 4 4
# 1 5 1
# 2 6 1
df.nunique()
# A 3
# B 2
# dtype: int64
1
2
3
4
5
df.nunique(axis=1)
# 0 1
# 1 2
# 2 2
# dtype: int64

value_counts()

相似的,value_counts用于计算一个Series中各值出现的频率:

1
2
3
4
5
6
print(obj.value_counts())
# c 3
# a 3
# b 2
# d 1
# dtype: int64

为了便于查看,结果Series是按值频率降序排列的。value_counts还是一个顶级pandas方法,可用于任何数组或序列:

1
2
3
4
5
6
print(pd.value_counts(obj.values, sort=False))
# c 3
# a 3
# d 1
# b 2
# Name: count, dtype: int64

isin用于判断矢量化集合的成员资格,可用于过滤Series中或DataFrame列中数据的子集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
print(obj)
# 0 c
# 1 a
# 2 d
# 3 a
# 4 a
# 5 b
# 6 b
# 7 c
# 8 c
# dtype: object
mask = obj.isin(['b','c'])
print(mask)
# 0 True
# 1 False
# 2 False
# 3 False
# 4 False
# 5 True
# 6 True
# 7 True
# 8 True
# dtype: bool

print(obj[mask])
# 0 c
# 5 b
# 6 b
# 7 c
# 8 c
# dtype: object

与isin类似的是Index.get_indexer方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组:

1
2
3
4
to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a'])
unique_vals = pd.Series(['c', 'b', 'a'])
print(pd.Index(unique_vals).get_indexer(to_match))
# [0 2 1 1 0 2]

表5-9给出了这几个方法的一些参考信息。

表5-9 唯一值、值计数、成员资格方法

统计函数一览表

函数 公式 含义
describe() DataFrame.describe(percentiles=None, include=None, exclude=None) 描述性统计(一次性返回多个统计结果)
min() DataFrame.min(axis=0, skipna=True, numeric_only=False, **kwargs) 计算最小值
max() DataFrame.max(axis=0, skipna=True, numeric_only=False, **kwargs) 计算最大值
sum() DataFrame.sum(axis=None, skipna=True, numeric_only=False, min_count=0, **kwargs) 求和
mean() DataFrame.mean(axis=0, skipna=True, numeric_only=False, **kwargs) 计算平均值
count() DataFrame.count(axis=0, numeric_only=False) 计数(统计非缺失元素的个数)
size DataFrame.size 计数(统计所有元素的个数)
median() DataFrame.median(axis=0, skipna=True, numeric_only=False, **kwargs) 计算中位数
var() DataFrame.var(axis=None, skipna=True, ddof=1, numeric_only=False, **kwargs) 计算方差
std() DataFrame.std(axis=None, skipna=True, ddof=1, numeric_only=False, **kwargs) 计算标准差
quantile() DataFrame.quantile(q=0.5, axis=0, numeric_only=False, interpolation=’linear’, method=’single’) 计算任意分位数,如四分位数q=0.25
cov() DataFrame.cov(min_periods=None, ddof=1, numeric_only=False) 计算协方差
corr() DataFrame.corr(method=’pearson’, min_periods=1, numeric_only=False) 计算相关系数
skew() DataFrame.skew(axis=0, skipna=True, numeric_only=False, **kwargs) 计算偏度
kurt() DataFrame.kurt(axis=0, skipna=True, numeric_only=False, **kwargs) 计算峰度
mode() DataFrame.mode(axis=0, numeric_only=False, dropna=True) 计算众数
groupby() DataFrame.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, observed=False, dropna=True) 分组
aggregate() DataFrame.aggregate(func=None, axis=0, *args, **kwargs) 聚合运算(可以自定义统计函数)
idxmin() DataFrame.idxmin(axis=0, skipna=True, numeric_only=False) 寻找最小值所在位置
idxmax() DataFrame.idxmax(axis=0, skipna=True, numeric_only=False) 寻找最大值所在位置
any() DataFrame.any(*, axis=0, bool_only=None, skipna=True, **kwargs) 等价于逻辑“或”
all() DataFrame.all(axis=0, bool_only=None, skipna=True, **kwargs) 等价于逻辑“与”
value_counts() DataFrame.value_counts(subset=None, normalize=False, sort=True, ascending=False, dropna=True) 频次统计
cumsum() DataFrame.cumsum(axis=None, skipna=True, *args, **kwargs) 运算累计和
cumprod() DataFrame.cumprod(axis=None, skipna=True, *args, **kwargs) 运算累计积

数据加载与存储

Excel文件

pandas的ExcelFile类或pandas.read_excel函数支持读取存储在Excel 2003(或更高版本)中的表格型数据。这两个工具分别使用扩展包xlrd和openpyxl读取XLS和XLSX文件。你可以用pip或conda安装它们。

要使用ExcelFile,通过传递xls或xlsx路径创建一个实例:

1
In [104]: xlsx = pd.ExcelFile('examples/ex1.xlsx')

存储在表单中的数据可以read_excel读取到DataFrame:

1
2
3
4
5
6
In [105]: pd.read_excel(xlsx, 'Sheet1')
Out[105]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

如果要读取一个文件中的多个表单,创建ExcelFile会更快,但你也可以将文件名传递到pandas.read_excel:

1
2
3
4
5
6
7
8
In [106]: frame = pd.read_excel('examples/ex1.xlsx', 'Sheet1')

In [107]: frame
Out[107]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

to_excel

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd

# 文件路径
file_path = 'E:/desktop/新建的空白文件.xlsx'

# 文件内容
# df = pd.DataFrame()
df = pd.DataFrame({'id':[1,2,3],'姓名':['张三','李四','王五']})

# 保存到文档
df.to_excel(file_path)

如果要将pandas数据写入为Excel格式,你必须首先创建一个ExcelWriter,然后使用pandas对象的to_excel方法将数据写入到其中:

1
2
3
4
5
In [108]: writer = pd.ExcelWriter('examples/ex2.xlsx')

In [109]: frame.to_excel(writer, 'Sheet1')

In [110]: writer.save()

你还可以不使用ExcelWriter,而是传递文件的路径到to_excel:

1
In [111]: frame.to_excel('examples/ex2.xlsx')

set_index

设置索引

DataFrame.set_index(keys, drop=True, append=False, inplace=False, verify_integrity=False)

参数:

  • keys: 列名或列名的列表。
  • drop: 布尔值,如果为真,将删除用于索引的列。
  • append: 如果为真,将该列添加到现有的索引列中。
  • inplace: 在数据框架中进行更改,如果是真的。
  • verify_integrity: 检查新的索引列是否重复,如果是的话。

更改索引列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pandas as pd

# 文件路径
file_path = 'E:/desktop/新建的空白文件.xlsx'

# 文件内容
df = pd.DataFrame({'id':[1,2,3],'姓名':['张三','李四','王五']})

# 设置id为索引列
df = df.set_index('id')

# 保存到文档
df.to_excel(file_path)

# 输出结果
# id 姓名
# 1 张三
# 2 李四
# 3 王五

CSV & 文本文件

read_csv()

常用参数

  • filepath_or_buffer: 文件路径,URL或者具有read()方法的任何对象(比如可打开的文件)

  • sep: str,默认分隔符为, , read_table()方法,分隔符为 \t

  • header: 当选择默认值或header=0时,将首行设为列名。如果列名被传入明确值就令header=None。注意,当header=0时,即使列名被传参也会被覆盖。

  • names: 列名列表的使用. 如果文件不包含列名,那么应该设置header=None。 列名列表中不允许有重复值。

  • index_col: 索引的列号或列名,可以是一个字符串名称或者数字,也可以是一个分层索引。

  • skiprows: 从文件开始处,需要跳过的行数或行号列表。

  • encoding: 文本编码,例如utf-8

  • nrows: 从文件开头处读入的行数

首先我们来看一个以逗号分隔的(CSV)文本文件:

1
2
3
4
5
In [8]: !cat examples/ex1.csv
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

由于该文件以逗号分隔,所以我们可以使用read_csv将其读入一个DataFrame:

1
2
3
4
5
6
7
8
In [9]: df = pd.read_csv('examples/ex1.csv')

In [10]: df
Out[10]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

read_table()

我们还可以使用read_table,并指定分隔符:

1
2
3
4
5
6
In [11]: pd.read_table('examples/ex1.csv', sep=',')
Out[11]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

并不是所有文件都有标题行。看看下面这个文件:

1
2
3
4
In [12]: !cat examples/ex2.csv
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

自定义行/列名

读入该文件的办法有两个。你可以让pandas为其分配默认的列名,也可以自己定义列名names

1
2
3
4
5
6
7
8
9
10
11
12
13
In [13]: pd.read_csv('examples/ex2.csv', header=None)
Out[13]:
0 1 2 3 4
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

In [14]: pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])
Out[14]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

假设你希望将message列做成DataFrame的索引。你可以明确表示要将该列放到索引4的位置上,也可以通过index_col参数指定”message”:

1
2
3
4
5
6
7
8
9
In [15]: names = ['a', 'b', 'c', 'd', 'message']

In [16]: pd.read_csv('examples/ex2.csv', names=names, index_col='message')
Out[16]:
a b c d
message
hello 1 2 3 4
world 5 6 7 8
foo 9 10 11 12

如果希望将多个列做成一个层次化索引,只需传入由列编号或列名组成的列表即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
In [17]: !cat examples/csv_mindex.csv
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16

In [18]: parsed = pd.read_csv('examples/csv_mindex.csv',
....: index_col=['key1', 'key2'])

In [19]: parsed
Out[19]:
value1 value2
key1 key2
one a 1 2
b 3 4
c 5 6
d 7 8
two a 9 10
b 11 12
c 13 14
d 15 16

有些情况下,有些表格可能不是用固定的分隔符去分隔字段的(比如空白符或其它模式)。看看下面这个文本文件:

1
2
3
4
5
6
7
In [20]: list(open('examples/ex3.txt'))
Out[20]:
[' A B C\n',
'aaa -0.264438 -1.026059 -0.619500\n',
'bbb 0.927272 0.302904 -0.032399\n',
'ccc -0.264273 -0.386314 -0.217601\n',
'ddd -0.871858 -0.348382 1.100491\n']

自定义分隔符

虽然可以手动对数据进行规整,这里的字段是被数量不同的空白字符间隔开的。这种情况下,你可以传递一个正则表达式作为read_table的分隔符。可以用正则表达式表达为\s+,于是有:

1
2
3
4
5
6
7
8
9
In [21]: result = pd.read_table('examples/ex3.txt', sep='\s+')

In [22]: result
Out[22]:
A B C
aaa -0.264438 -1.026059 -0.619500
bbb 0.927272 0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382 1.100491

这里,由于列名比数据行的数量少,所以read_table推断第一列应该是DataFrame的索引。

这些解析器函数还有许多参数可以帮助你处理各种各样的异形文件格式(表6-2列出了一些)。比如说,你可以用skiprows跳过文件的第一行、第三行和第四行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [23]: !cat examples/ex4.csv
# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
In [24]: pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3])
Out[24]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo

缺失值处理是文件解析任务中的一个重要组成部分。缺失数据经常是要么没有(空字符串),要么用某个标记值表示。默认情况下,pandas会用一组经常出现的标记值进行识别,比如NA及NULL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [25]: !cat examples/ex5.csv
something,a,b,c,d,message
one,1,2,3,4,NA
two,5,6,,8,world
three,9,10,11,12,foo
In [26]: result = pd.read_csv('examples/ex5.csv')

In [27]: result
Out[27]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo

In [28]: pd.isnull(result)
Out[28]:
something a b c d message
0 False False False False False True
1 False False False True False False
2 False False False False False False

na_values可以用一个列表或集合的字符串表示缺失值:

1
2
3
4
5
6
7
8
In [29]: result = pd.read_csv('examples/ex5.csv', na_values=['NULL'])
# 空值被替换成NaN
In [30]: result
Out[30]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo

字典的各列可以使用不同的NA标记值:

1
2
3
4
5
6
7
8
In [31]: sentinels = {'message': ['foo', 'NA'], 'something': ['two']}

In [32]: pd.read_csv('examples/ex5.csv', na_values=sentinels)
Out[32]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 NaN 5 6 NaN 8 world
2 three 9 10 11.0 12 NaN

表6-2列出了pandas.read_csv和pandas.read_table常用的选项。

逐块读取文本文件

在处理很大的文件时,或找出大文件中的参数集以便于后续处理时,你可能只想读取文件的一小部分或逐块对文件进行迭代。

在看大文件之前,我们先设置pandas显示地更紧些:

1
In [33]: pd.options.display.max_rows = 10

然后有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [34]: result = pd.read_csv('examples/ex6.csv')

In [35]: result
Out[35]:
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 Q
... ... ... ... ... ..
9995 2.311896 -0.417070 -1.409599 -0.515821 L
9996 -0.479893 -0.650419 0.745152 -0.646038 E
9997 0.523331 0.787112 0.486066 1.093156 K
9998 -0.362559 0.598894 -1.843201 0.887292 G
9999 -0.096376 -1.012999 -0.657431 -0.573315 0
[10000 rows x 5 columns]
nrows

如果只想读取几行(避免读取整个文件),通过nrows进行指定即可:

1
2
3
4
5
6
7
8
In [36]: pd.read_csv('examples/ex6.csv', nrows=5)
Out[36]:
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 Q
chunksize

要逐块读取文件,可以指定chunksize(行数):

1
2
3
4
In [874]: chunker = pd.read_csv('ch06/ex6.csv', chunksize=1000)

In [875]: chunker
Out[875]: <pandas.io.parsers.TextParser at 0x8398150>

read_csv所返回的这个TextParser对象使你可以根据chunksize对文件进行逐块迭代。比如说,我们可以迭代处理ex6.csv,将值计数聚合到”key”列中,如下所示:

1
2
3
4
5
6
7
chunker = pd.read_csv('examples/ex6.csv', chunksize=1000)

tot = pd.Series([])
for piece in chunker:
tot = tot.add(piece['key'].value_counts(), fill_value=0)

tot = tot.sort_values(ascending=False)

然后有:

1
2
3
4
5
6
7
8
9
10
11
12
13
In [40]: tot[:10]
Out[40]:
E 368.0
X 364.0
L 346.0
O 343.0
Q 340.0
M 338.0
J 337.0
F 335.0
K 334.0
H 330.0
dtype: float64

TextParser还有一个get_chunk方法,它使你可以读取任意大小的块。

to_csv()

利用DataFrame的to_csv方法,我们可以将数据写到一个以逗号分隔的文件中:

DataFrame.to_csv(path_or_buf="", sep=",")

常用参数

  • path_or_buf:必填项,要写入的文件或文件对象的字符串路径。如果是文件对象,则必须使用打开该对象
  • sep:输出文件的字段分隔符(默认为“,”)
  • na_rep: 缺失数据填充,(默认为””)
  • columns:要写入的列(默认为“无”)
  • header:是否写出列名(默认为True)
  • index:是否写入行(索引)名称(默认为True)
  • index_label: 索引列的列标签(如果需要)。如果无(默认值),并且headerindex为True,则使用索引名称。(如果DataFrame使用MultiIndex,则应给出序列)。
  • mode:Python写入模式,默认为“w”
  • encoding:输出文件中使用的编码格式,默认为”utf-8”
1
2
3
4
5
6
7
8
In [41]: data = pd.read_csv('examples/ex5.csv')

In [42]: data
Out[42]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo
1
2
3
4
5
6
7
In [43]: data.to_csv('examples/out.csv')

In [44]: !cat examples/out.csv
,something,a,b,c,d,message
0,one,1,2,3.0,4,
1,two,5,6,,8,world
2,three,9,10,11.0,12,foo

当然,还可以使用其他分隔符(由于这里直接写出到sys.stdout,所以仅仅是打印出文本结果而已):

1
2
3
4
5
6
7
8
In [45]: import sys

In [46]: data.to_csv(sys.stdout, sep='|')
# Python中的sys.stdout是一个内置模块sys中的属性,代表了标准输出流,也就是默认情况下Python程序将输出的信息打印到屏幕上的地方。
|something|a|b|c|d|message
0|one|1|2|3.0|4|
1|two|5|6||8|world
2|three|9|10|11.0|12|foo

缺失值在输出结果中会被表示为空字符串。你可能希望将其表示为别的标记值:

1
2
3
4
5
In [47]: data.to_csv(sys.stdout, na_rep='NULL')
,something,a,b,c,d,message
0,one,1,2,3.0,4,NULL
1,two,5,6,NULL,8,world
2,three,9,10,11.0,12,foo

如果没有设置其他选项,则会写出行和列的标签。当然,它们也都可以被禁用:

1
2
3
4
In [48]: data.to_csv(sys.stdout, index=False, header=False)
one,1,2,3.0,4,
two,5,6,,8,world
three,9,10,11.0,12,foo

此外,你还可以只写出一部分的列,并以你指定的顺序排列:

1
2
3
4
5
In [49]: data.to_csv(sys.stdout, index=False, columns=['a', 'b', 'c'])
a,b,c
1,2,3.0
5,6,
9,10,11.0

Series也有一个to_csv方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [50]: dates = pd.date_range('1/1/2000', periods=7)

In [51]: ts = pd.Series(np.arange(7), index=dates)

In [52]: ts.to_csv('examples/tseries.csv')

In [53]: !cat examples/tseries.csv
2000-01-01,0
2000-01-02,1
2000-01-03,2
2000-01-04,3
2000-01-05,4
2000-01-06,5
2000-01-07,6

处理分隔符格式

大部分存储在磁盘上的表格型数据都能用pandas.read_table进行加载。然而,有时还是需要做一些手工处理。由于接收到含有畸形行的文件而使read_table出毛病的情况并不少见。为了说明这些基本工具,看看下面这个简单的CSV文件:

1
2
3
4
In [54]: !cat examples/ex7.csv
"a","b","c"
"1","2","3"
"1","2","3"

对于任何单字符分隔符文件,可以直接使用Python内置的csv模块。将任意已打开的文件或文件型的对象传给csv.reader:

1
2
3
4
import csv
f = open('examples/ex7.csv')

reader = csv.reader(f)

对这个reader进行迭代将会为每行产生一个元组(并移除了所有的引号):

1
2
3
4
5
In [56]: for line in reader:
....: print(line)
['a', 'b', 'c']
['1', '2', '3']
['1', '2', '3']

现在,为了使数据格式合乎要求,你需要对其做一些整理工作。我们一步一步来做。首先,读取文件到一个多行的列表中:

1
2
In [57]: with open('examples/ex7.csv') as f:
....: lines = list(csv.reader(f))

然后,我们将这些行分为标题行和数据行:

1
In [58]: header, values = lines[0], lines[1:]

然后,我们可以用字典构造式和zip(*values),后者将行转置为列,创建数据列的字典:

1
2
3
4
In [59]: data_dict = {h: v for h, v in zip(header, zip(*values))}

In [60]: data_dict
Out[60]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}

CSV文件的形式有很多。只需定义csv.Dialect的一个子类即可定义出新格式(如专门的分隔符、字符串引用约定、行结束符等):

1
2
3
4
5
6
class my_dialect(csv.Dialect):
lineterminator = '\n'
delimiter = ';'
quotechar = '"'
quoting = csv.QUOTE_MINIMAL
reader = csv.reader(f, dialect=my_dialect)

各个CSV语支的参数也可以用关键字的形式提供给csv.reader,而无需定义子类:

1
reader = csv.reader(f, delimiter='|')

可用的选项(csv.Dialect的属性)及其功能如表6-3所示。

笔记:对于那些使用复杂分隔符或多字符分隔符的文件,csv模块就无能为力了。这种情况下,你就只能使用字符串的split方法或正则表达式方法re.split进行行拆分和其他整理工作了。

要手工输出分隔符文件,你可以使用csv.writer。它接受一个已打开且可写的文件对象以及跟csv.reader相同的那些语支和格式化选项:

1
2
3
4
5
6
with open('mydata.csv', 'w') as f:
writer = csv.writer(f, dialect=my_dialect)
writer.writerow(('one', 'two', 'three'))
writer.writerow(('1', '2', '3'))
writer.writerow(('4', '5', '6'))
writer.writerow(('7', '8', '9'))

缺失数据处理

在许多数据分析工作中,缺失数据是经常发生的。pandas的目标之一就是尽量轻松地处理缺失数据。例如,pandas对象的所有描述性统计默认都不包括缺失数据。

缺失数据在pandas中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。我们称其为哨兵值,可以方便的检测出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [10]: string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado'])

In [11]: string_data
Out[11]:
0 aardvark
1 artichoke
2 NaN
3 avocado
dtype: object

In [12]: string_data.isnull()
Out[12]:
0 False
1 False
2 True
3 False
dtype: bool

在pandas中,我们采用了R语言中的惯用法,即将缺失值表示为NA,它表示不可用not available。在统计应用中,NA数据可能是不存在的数据或者虽然存在,但是没有观察到(例如,数据采集中发生了问题)。当进行数据清洗以进行分析时,最好直接对缺失数据进行分析,以判断数据采集的问题或缺失数据可能导致的偏差。

Python内置的None值在对象数组中也可以作为NA:

1
2
3
4
5
6
7
8
9
In [13]: string_data[0] = None

In [14]: string_data.isnull()
Out[14]:
0 True
1 False
2 True
3 False
dtype: bool

滤除缺失数据

过滤掉缺失数据的办法有很多种。你可以通过pandas.isnull或布尔索引的手工方法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:

dropna()

DataFrame.dropna(*, axis=0, how=_NoDefault.no_default, thresh=_NoDefault.no_default, subset=None, inplace=False, ignore_index=False)

  • axis:可以是0或1,表示删除行或者列。默认为0。
  • how:删除的方式,可以是’any’或’all’。’any’表示只要存在缺失值就删除,’all’表示所有的值都是缺失值才删除。默认为’any’。
  • thresh:可以是整数,表示这一行或列最少要有多少个非缺失值才不被删除。如果设置为None,表示所有数据值都需要进行判断。默认为None。
  • subset:可以是列名称或列名称的列表,表示只在这些列中进行删除操作。默认为None。
  • inplace:True表示直接修改原数据集;False表示返回删除后的新数据集。默认为False。
1
2
3
4
5
6
7
8
9
10
In [15]: from numpy import nan as NA

In [16]: data = pd.Series([1, NA, 3.5, NA, 7])

In [17]: data.dropna()
Out[17]:
0 1.0
2 3.5
4 7.0
dtype: float64

这等价于:

1
2
3
4
5
6
In [18]: data[data.notnull()]
Out[18]:
0 1.0
2 3.5
4 7.0
dtype: float64

而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [19]: data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA],
....: [NA, NA, NA], [NA, 6.5, 3.]])

In [20]: cleaned = data.dropna()

In [21]: data
Out[21]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
2 NaN NaN NaN
3 NaN 6.5 3.0

In [22]: cleaned
Out[22]:
0 1 2
0 1.0 6.5 3.0

传入how=’all’将只丢弃全为NA的那些行:

1
2
3
4
5
6
In [23]: data.dropna(how='all')
Out[23]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
3 NaN 6.5 3.0

用这种方式丢弃列,只需传入axis=1即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [24]: data[4] = NA

In [25]: data
Out[25]:
0 1 2 4
0 1.0 6.5 3.0 NaN
1 1.0 NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN 6.5 3.0 NaN

In [26]: data.dropna(axis=1, how='all')
Out[26]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
2 NaN NaN NaN
3 NaN 6.5 3.0

另一个滤除DataFrame行的问题涉及时间序列数据。假设你只想留下一部分观测数据,可以用thresh参数实现此目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
In [27]: df = pd.DataFrame(np.random.randn(7, 3))

In [28]: df.iloc[:4, 1] = NA

In [29]: df.iloc[:2, 2] = NA

In [30]: df
Out[30]:
0 1 2
0 -0.204708 NaN NaN
1 -0.555730 NaN NaN
2 0.092908 NaN 0.769023
3 1.246435 NaN -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

In [31]: df.dropna()
Out[31]:
0 1 2
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

In [32]: df.dropna(thresh=2)
Out[32]:
0 1 2
2 0.092908 NaN 0.769023
3 1.246435 NaN -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

填充缺失数据

你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值:

fillna()

DataFrame.fillna(value=None, *, method=None, axis=None, inplace=False, limit=None, downcast=None)

  • value:用于填充缺失值的值,可以是标量、字典、Series 或 DataFrame。
  • method:填充缺失值的方法,可选值包括 backfill(向前填充)、bfill(向后填充)、pad(用前面的非缺失数据填充)、ffill(用后面的非缺失数据填充)等。
  • axis:指定在哪个轴上执行填充操作。
  • inplace:是否在原 DataFrame 上直接进行修改。
  • limit:对于前向填充和后向填充,限制填充缺失值的最大数量。
  • downcast:指定填充后的数据类型,可选值包括infer(自动推断)、integer(整型)等。
1
2
3
4
5
6
7
8
9
10
In [33]: df.fillna(0)
Out[33]:
0 1 2
0 -0.204708 0.000000 0.000000
1 -0.555730 0.000000 0.000000
2 0.092908 0.000000 0.769023
3 1.246435 0.000000 -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值

1
2
3
4
5
6
7
8
9
10
In [34]: df.fillna({1: 0.5, 2: 0})
Out[34]:
0 1 2
0 -0.204708 0.500000 0.000000
1 -0.555730 0.500000 0.000000
2 0.092908 0.500000 0.769023
3 1.246435 0.500000 -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

fillna默认会返回新对象,但也可以对现有对象进行就地修改:

1
2
3
4
5
6
7
8
9
10
11
12
In [35]: _ = df.fillna(0, inplace=True)

In [36]: df
Out[36]:
0 1 2
0 -0.204708 0.000000 0.000000
1 -0.555730 0.000000 0.000000
2 0.092908 0.000000 0.769023
3 1.246435 0.000000 -1.296221
4 0.274992 0.228913 1.352917
5 0.886429 -2.001637 -0.371843
6 1.669025 -0.438570 -0.539741

对reindexing有效的那些插值方法也可用于fillna:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
In [37]: df = pd.DataFrame(np.random.randn(6, 3))

In [38]: df.iloc[2:, 1] = NA

In [39]: df.iloc[4:, 2] = NA

In [40]: df
Out[40]:
0 1 2
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
2 0.523772 NaN 1.343810
3 -0.713544 NaN -2.370232
4 -1.860761 NaN NaN
5 -1.265934 NaN NaN

In [41]: df.fillna(method='ffill')
Out[41]:
0 1 2
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
2 0.523772 0.124121 1.343810
3 -0.713544 0.124121 -2.370232
4 -1.860761 0.124121 -2.370232
5 -1.265934 0.124121 -2.370232

In [42]: df.fillna(method='ffill', limit=2)
Out[42]:
0 1 2
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
2 0.523772 0.124121 1.343810
3 -0.713544 0.124121 -2.370232
4 -1.860761 NaN -2.370232
5 -1.265934 NaN -2.370232

只要有些创新,你就可以利用fillna实现许多别的功能。比如说,你可以传入Series的平均值或中位数:

1
2
3
4
5
6
7
8
9
10
In [43]: data = pd.Series([1., NA, 3.5, NA, 7])

In [44]: data.fillna(data.mean())
Out[44]:
0 1.000000
1 3.833333
2 3.500000
3 3.833333
4 7.000000
dtype: float64

数据转换

移除重复数据

DataFrame中出现重复行有多种原因。下面就是一个例子:

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]})
print(data)
# k1 k2
# 0 one 1
# 1 two 1
# 2 one 2
# 3 two 3
# 4 one 3
# 5 two 4
# 6 two 4

duplicated()

DataFrame的duplicated方法返回一个布尔型Series,表示各行是否是重复行(前面出现过的行):

1
2
3
4
5
6
7
8
9
print(data.duplicated())
# 0 False
# 1 False
# 2 False
# 3 False
# 4 False
# 5 False
# 6 True
# dtype: bool

drop_duplicates()

df.drop_duplicates(subset=['A','B'],keep='first',inplace=True)

  • subset: 输入要进行去重的列名,默认为None

  • keep: 可选参数有三个:‘first’、 ‘last’、 False, 默认值为 ‘first’。其中,first表示: 保留第一次出现的重复行,删除后面的重复行。last表示: 删除重复项,保留最后一次出现。False表示: 删除所有重复项。

  • inplace:布尔值,默认为False,是否直接在原数据上删除重复项或删除重复项后返回副本。

1
2
3
4
5
6
7
8
print(data.drop_duplicates())
# k1 k2
# 0 one 1
# 1 two 1
# 2 one 2
# 3 two 3
# 4 one 3
# 5 two 4

这两个方法默认会判断全部列,你也可以指定部分列进行重复项判断。假设我们还有一列值,且只希望根据k1列过滤重复项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]})
data['v1'] = range(7)
print(data)
# k1 k2 v1
# 0 one 1 0
# 1 two 1 1
# 2 one 2 2
# 3 two 3 3
# 4 one 3 4
# 5 two 4 5
# 6 two 4 6
print(data.drop_duplicates(['k1']))
# k1 k2 v1
# 0 one 1 0
# 1 two 1 1
print(data.drop_duplicates(['k1'],keep='last'))
# k1 k2 v1
# 4 one 3 4
# 6 two 4 6

矢量化字符串方法

Series 支持字符串处理方法,可以非常方便地操作数组里的每个元素。这些方法会自动排除缺失值与空值,这也许是其最重要的特性。这些方法通过 Series 的 str 属性访问,一般情况下,这些操作的名称与内置的字符串方法一致。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])

print(s.str.lower())
# 0 a
# 1 b
# 2 c
# 3 aaba
# 4 baca
# 5 NaN
# 6 caba
# 7 dog
# 8 cat
# dtype: object

.dt 访问器

对于一个datetime类型的字段,在dataframe中日期时间类型的列数据也可以进行分割处理,即应用属性接口dt

Series 提供一个可以简单、快捷地返回 datetime 属性值的访问器。这个访问器返回的也是 Series,索引与现有的 Series 一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
# datetime
s = pd.Series(pd.date_range('20230706 16:13:12', periods=3))
print(s)
# 0 2023-07-06 16:13:12
# 1 2023-07-07 16:13:12
# 2 2023-07-08 16:13:12
# dtype: datetime64[ns]
print(s.dt.hour)
# 0 16
# 1 16
# 2 16
# dtype: int32
print(s.dt.second)
# 0 12
# 1 12
# 2 12
# dtype: int32
print(s.dt.day)
# 0 6
# 1 7
# 2 8
# dtype: int32

还可以用 Series.dt.strftime()datetime 的值当成字符串进行格式化,支持与标准 strftime()同样的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# DatetimeIndex
s = pd.Series(pd.date_range('20130101', periods=4))

# 0 2013-01-01
# 1 2013-01-02
# 2 2013-01-03
# 3 2013-01-04
# dtype: datetime64[ns]

print(s.dt.strftime('%Y/%m/%d'))
# 0 2013/01/01
# 1 2013/01/02
# 2 2013/01/03
# 3 2013/01/04
# dtype: object

# PeriodIndex
s = pd.Series(pd.period_range('20130101', periods=4))

print(s)
# 0 2013-01-01
# 1 2013-01-02
# 2 2013-01-03
# 3 2013-01-04
# dtype: period[D]

print(s.dt.strftime('%Y/%m/%d'))
# 0 2013/01/01
# 1 2013/01/02
# 2 2013/01/03
# 3 2013/01/04
# dtype: object
函数 含义
dt.date 年月日
dt.year
dt.month
dt.day
dt.hour
dt.minute
dt.second
dt.week 一年中的第几周
dt.weekday / dt.day_of_week 一周中的星期几(0代表星期一)
dt.day_name() 对于英文星期名称
dt.month_name() 对应英文月份名称
dt.dayofyear / dt.dat_of_year 一年中的第几天
dt.quarter 一年中的第几个季度
dt.is_leap_year 是否是闰年

日期处理

时间序列数据的意义取决于具体的应用场景,主要有以下几种:

  • 时间戳(timestamp),特定的时刻。
  • 固定时期(period),如2007年1月或2010年全年。
  • 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔(interval)的特例。
  • 实验或过程时间,每个时间点都是相对于特定起始时间的一个度量。例如,从放入烤箱时起,每秒钟饼干的直径。

Python标准库包含用于日期(date)和时间(time)数据的数据类型,而且还有日历方面的功能。我们主要会用到datetime、time以及calendar模块。datetime.datetime(也可以简写为datetime)是用得最多的数据类型:

日期和时间数据类型及工具

datetime.datetime

datetime.datetime(year, month, day, hour, minute, second, microsecond)

  • 获取某天日期时间。至少传入year, month, day三个参数。

datetime以毫秒形式存储日期和时间。

1
2
3
4
5
6
7
8
9
from datetime import datetime
delta = datetime(2023, 7, 6) - datetime(2023, 6, 6, 8, 15)

print(delta)
# 29 days, 15:45:00
print(delta.days)
# 29
print(delta.seconds)
# 56700

datetime.datetime.now()

1
2
3
4
5
6
7
from datetime import datetime
now = datetime.now()

print(now)
print(now.year,now.month,now.day)
# 2023-07-06 09:58:27.233489
# 2023 7 6

datetime.timedelta

datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)

  • 函数所有参数都是可选的并且默认为 0。 这些参数可以是整数或者浮点数,也可以是正数或者负数。
  • datetime.timedelta()返回类型为 datetime.timedelta

timedelta表示两个datetime对象之间的时间差。

可以给datetime对象加上(或减去)一个或多个timedelta,这样会产生一个新对象:

1
2
3
4
5
6
from datetime import timedelta,datetime
start = datetime(2011, 1, 7)
print(start + timedelta(12))
# 2011-01-19 00:00:00
print(start - 2 * timedelta(minutes=10))
# 2011-01-06 23:40:00

字符串和datetime的相互转换

datetime.strftime()

datetime对象.strftime(format)
datetime.strftime(datetime对象, format)

  • format: 格式化编码

利用str或strftime方法(传入一个格式化字符串),datetime对象和pandas的Timestamp对象可以被格式化为字符串:

1
2
3
4
5
6
7
8
9
10
11
12
from datetime import datetime
stamp = datetime(2023, 7, 6)

# 用str()将datetime转为字符串
print(str(stamp))
# 2023-07-06 00:00:00

# 用strftime()将datetime转为字符串
print(stamp.strftime('%Y-%m-%d'))
# 2023-07-06
print(datetime.strftime(stamp,"%Y-%m-%d"))
# 2023-07-06

datetime.strptime()

datetime.strptime(date_string, format)

  • date_string: 日期字符串

  • format: 格式化编码

datetime.strptime可以用这些格式化编码将字符串转换为datetime日期

1
2
3
4
5
6
from datetime import datetime
print(datetime.strptime('2023-07-06', '%Y-%m-%d'))

# DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
# '2012-05-06'],
# dtype='datetime64[ns]', freq='D')

datetime.strptime是通过已知格式进行日期解析的最佳方式。但是每次都要编写格式定义是很麻烦的事情,尤其是对于一些常见的日期格式。这种情况下,你可以用dateutil这个第三方包中的parser.parse方法(pandas中已经自动安装好了):

1
2
3
4
from dateutil.parser import parse

print(parse('2023-07-06'))
# 2023-07-06 00:00:00

dateutil可以解析几乎所有人类能够理解的日期表示形式:

1
2
print(parse('Jan 31, 1997 10:45 PM'))
# datetime.datetime(1997, 1, 31, 22, 45)

在国际通用的格式中,日出现在月的前面很普遍,传入dayfirst=True即可解决这个问题:

1
2
print(parse('6/12/2011', dayfirst=True))
# datetime.datetime(2011, 12, 6, 0, 0)

pandas.to_datetime()

当csv文件被导入并形成一个数据框架时,文件中的日期时间对象被读取为字符串对象,而不是日期时间对象,因此,对字符串而不是日期时间对象进行时间差等操作非常困难。Pandas的to_datetime()方法有助于将字符串日期时间转换成Python日期时间对象。

pandas.to_datetime (arg, errors=’raise’, dayfirst=False, yearfirst=False, utc=None, box=True, format=None, exact=True, unit=None, infer_datetime_format=False, origin=’unix’, cache=False)

  • arg: 一个整数、字符串、浮点数、列表或字典对象,用于转换为Date时间对象。

  • dayfirst: 布尔值,如果为真,则将日期放在首位。

  • yearfirst: 布尔值,如果为真,则将年份放在前面。

  • utc: 布尔值,如果为真,则返回UTC的时间。

  • format: 字符串输入,告诉日、月、年的位置。

to_datetime方法可以解析多种不同的日期表示形式。对标准日期格式(如ISO8601)的解析非常快:

1
2
3
4
import pandas as pd
datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00']
print(pd.to_datetime(datestrs))
# DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00'], dtype='datetime64[ns]', freq=None)

它还可以处理缺失值(None、空字符串等):

NaT(Not a Time)是pandas中时间戳数据的null值。

1
2
3
4
5
6
7
8
9
10
import pandas as pd
datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00']
idx = pd.to_datetime(datestrs + [None])

print(idx)
# DatetimeIndex(['2011-07-06 12:00:00', '2011-08-06 00:00:00', 'NaT'], dtype='datetime64[ns]', freq=None)
print(idx[2])
# NaT
print(pd.isnull(idx))
# [False False True]

格式化编码表

代码 说明
%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

时间序列

pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datatime对象表示)为索引的Series:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from datetime import datetime
import pandas as pd
import numpy as np
dates = [datetime(2011, 1, 2), datetime(2011, 1, 5),
datetime(2011, 1, 7), datetime(2011, 1, 8),
datetime(2011, 1, 10), datetime(2011, 1, 12)]
ts = pd.Series(np.random.randn(6), index=dates)
print(ts)
# 2011-01-02 -1.143138
# 2011-01-05 0.298419
# 2011-01-07 1.018143
# 2011-01-08 -0.579162
# 2011-01-10 -0.787679
# 2011-01-12 0.223588
# dtype: float64

这些datetime对象实际上是被放在一个DatetimeIndex中的:

1
2
3
4
print(ts.index)
# DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07', '2011-01-08',
# '2011-01-10', '2011-01-12'],
# dtype='datetime64[ns]', freq=None)

跟其他Series一样,不同索引的时间序列之间的算术运算会自动按日期对齐:

1
2
3
4
5
6
7
8
9
print(ts + ts[::2])
# ts[::2] 是每隔两个取一个。
# 2011-01-02 -2.286277
# 2011-01-05 NaN
# 2011-01-07 2.036286
# 2011-01-08 NaN
# 2011-01-10 -1.575358
# 2011-01-12 NaN
# dtype: float64

pandas用NumPy的datetime64数据类型以纳秒形式存储时间戳:

1
2
print(ts.index.dtype)
# datetime64[ns]

DatetimeIndex中的各个标量值是pandas的Timestamp对象:

1
2
3
stamp = ts.index[0]
print(stamp)
# Timestamp('2011-01-02 00:00:00')

只要有需要,TimeStamp可以随时自动转换为datetime对象。此外,它还可以存储频率信息(如果有的话),且知道如何执行时区转换以及其他操作。

日期的范围、频率以及移动

pandas.date_range()

pd.date_range(start=None, end=None, periods=None, freq=‘D’, tz=None, normalize=False, name=None, closed=None, **kwargs)

  • start:开始时间
  • end:结束时间
  • periods:持续时间
  • freq:频率,默认天
  • normalize:时间参数值正则化到午夜时间戳
  • name:索引对象名称
  • closed:默认为None的情况下,左闭右闭,left则左闭右开,right则左开右闭
  • pd.date_range()默认频率为日历日
  • pd.bdate_range()默认频率为工作日
  • tz:时区

pandas.date_range可用于根据指定的频率生成指定长度的DatetimeIndex:

1
2
3
4
5
6
7
8
9
10
11
12
import pandas as pd
index = pd.date_range('2023-07-01', '2023-08-01')
print(index)
# DatetimeIndex(['2023-07-01', '2023-07-02', '2023-07-03', '2023-07-04',
# '2023-07-05', '2023-07-06', '2023-07-07', '2023-07-08',
# '2023-07-09', '2023-07-10', '2023-07-11', '2023-07-12',
# '2023-07-13', '2023-07-14', '2023-07-15', '2023-07-16',
# '2023-07-17', '2023-07-18', '2023-07-19', '2023-07-20',
# '2023-07-21', '2023-07-22', '2023-07-23', '2023-07-24',
# '2023-07-25', '2023-07-26', '2023-07-27', '2023-07-28',
# '2023-07-29', '2023-07-30', '2023-07-31', '2023-08-01'],
# dtype='datetime64[ns]', freq='D')

默认情况下,date_range会产生按天计算的时间点。如果只传入起始或结束日期,那就还得传入一个表示一段时间的数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

index1 = pd.date_range(start="2023-07-06",periods=7)
print(index1)
# DatetimeIndex(['2023-07-06', '2023-07-07', '2023-07-08', '2023-07-09',
# '2023-07-10', '2023-07-11', '2023-07-12'],
# dtype='datetime64[ns]', freq='D')

index2 = pd.date_range(end="2023-07-06",periods=7)
print(index2)
# DatetimeIndex(['2023-06-30', '2023-07-01', '2023-07-02', '2023-07-03',
# '2023-07-04', '2023-07-05', '2023-07-06'],
# dtype='datetime64[ns]', freq='D')

date_range默认会保留起始和结束时间戳的时间信息(如果有的话):

1
2
3
4
5
print(pd.date_range('2012-05-02 12:56:31', periods=5))
# DatetimeIndex(['2012-05-02 12:56:31', '2012-05-03 12:56:31',
# '2012-05-04 12:56:31', '2012-05-05 12:56:31',
# '2012-05-06 12:56:31'],
# dtype='datetime64[ns]', freq='D')

有时,虽然起始和结束日期带有时间信息,但你希望产生一组被规范化(normalize)到午夜的时间戳。normalize选项即可实现该功能:

1
2
3
4
print(pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True))
# DatetimeIndex(['2012-05-02', '2012-05-03', '2012-05-04', '2012-05-05',
# '2012-05-06'],
# dtype='datetime64[ns]', freq='D')