《深入浅出Pandas》看完觉得比较好,把里面的命令记录下来,用于实际使用被查。

第2章 数据结构

2.2 Python的数据结构

type(123) 
a = "Hello"
type(a) 
isinstance(123, int) 
isinstance('123', int)
isinstance(True, bool)
2.2.1 数字
x = 1    # int, 整型
y = 1.2  # float, 浮点
z = 1j   # complex, 复数
a = 10
b = 21
# 数值计算
a + b # 31
a - b # -11
a * b # 210
b / a # 2.1
a ** b # 表示10的21次幂
b % a # 1 (取余)
9//2 # 4
9.0//2.0 # 4.0
-11//3 # -4
-11.0//3 # -4.0
2.2.2 字符串
var = 'Hello World!'
# 按索引取部分内容,索引从0开始, 左必须小于右
# 支持字符、列表、元组
var[0] # 'H'
# 从右往左,索引从-1开始
var[-1] # '!'
var[-3:-1] # 'ld'
var[1:7] # 'ello W'(有个空格,不包含最后一位索引7)
var[6:] # 'World!' (只指定开头,包含后面所有的字符)
var[:] # 'Hello World!'(相当于复制)
var[0:5:2] # 'Hlo'(2为步长,按2的倍数取)
var[1:7:3] # 'ello W' -> 'eo'
var[::-1] # '!dlroW olleH'(实现反转字符功能)
len('good') # 4 (字符的长度)
'good'.replace('g', 'G') # 'Good' (替换字符)
'山-水-风-雨'.split('-') # ['山', '水', '风', '雨'] (用指定字符分隔,默认空格)
'好山好水好风光'.split('好') # ['', '山', '水', '风光']
'-'.join(['山','水','风','雨']) # '山-水-风-雨'
'和'.join(['诗', '远方']) # '诗和远方'
'good'.upper() # 'GOOD' (全转大写)
'GOOD'.lower() # 'good' (全转小写)
'Good Bye'.swapcase() # 'gOOD bYE' (大小写互换)
'good'.capitalize() # 'Good' (首字母转大写)
'good'.islower() # True (是否全是小写)
'good'.isupper() # False (是否全是大写)
'3月'.zfill(3) # '03月' (指定长度,如长度不够,前面补0)
2.2.3 布尔型
a = 0
b = 1
c = 2
a and b # 0(a为假,返回假的值)
b and a # 0(b为真,返回a的值)
a or b # 2 (a为假,返回b的值)
a and b or c # 2
a and (b or c) # 0 (用类似数学中的括号提高运算优先级)
# not的注意事项
not a # True
not a == b # True
not (a == b) # True(逻辑同上)
a == not b # 这条有语法错误, 正确的如下:
a == (not b) # True
# and的优先级高于or。首先,'a'为真,'a' and 'b'返回'b';然后,'' or 'b'返回'b'
'' or 'a' and 'b' # 'b'
2.2.4 列表
x = [] # 空列表
x = [1, 2, 3, 4, 5]
x = ['a', 'b', 'c']
x = ['a', 1.5, True, [2, 3, 4]] # 各种类型混杂
type(x) # list 类型检测
a = [1, 2, 3]
len(a) # 3(元素个数)
max(a) # 3(最大值)
min(a) # 1(最小值)
sum(a) # 6(求和)
a.index(2) # 1(指定元素位置)
a.count(1) # 1(求元素的个数)
for i in a: print(i) # 迭代元素
sorted(a) # 返回一个排序的列表,但不改变原列表
any(a) # True(是否至少有一个元素为真)
all(a) # True(是否所有元素为真)
a.append(4) # a: [1, 2, 3, 4](增加一个元素)
a.pop() # 每执行一次,删除最后一个元素
a.extend([9,8]) # a: [1, 2, 3, 9, 8](与其他列表合并)
a.insert(1, 'a') # a: [1, 'a', 2, 3](在指定索引位插入元素,索引从0开始)
a.remove('a') # 删除第一个指定元素
a.clear() # [](清空)
# 将一个可迭代的对象展开,形成一个列表
[i for i in range(5)]
# 可以将结果进行处理
['第'+str(i) for i in range(5)]
# ['第0', '第1', '第2', '第3', '第4']
# 可以进行条件筛选,实现取偶数
[i for i in range(5) if i%2==0]
# 拆开字符,过滤空格,全变成大写
[i.upper() for i in 'Hello world' if i != ' ']
2.2.5 元组
x = (1,2,3,4,5)
a, *b = x # a占第一个,剩余的组成列表全给b
a, *b, c = x # a占第一个,c占最后一个,剩余的组成列表全给b
2.2.6 字典
d = {} # 定义空字典
d = dict() # 定义空字典
d = {'a': 1, 'b': 2, 'c': 3}
d = {'a': 1, 'a': 1, 'a': 1} # {'a': 1} key不能重复,重复时取最后一个
d = {'a': 1, 'b': {'x': 3}} # 嵌套字典
d = {'a': [1,2,3], 'b': [4,5,6]} # 嵌套列表

# 以下均可定义如下结果
# {'name': 'Tom', 'age': 18, 'height': 180}
d = dict(name='Tom', age=18, height=180)
d = dict([('name', 'Tom'), ('age', 18), ('height', 180)])
d = dict(zip(['name', 'age', 'height'], ['Tom', 18, 180]))

d['name']  # 'Tom'(获取键的值)
d['age'] = 20  # 将age的值更新为20
d['Female'] = 'man'  # 增加属性
d.get('height', 180)  # 180

# 嵌套取值
d = {'a': {'name': 'Tom', 'age':18}, 'b': [4,5,6]}
d['b'][1] # 5
d['a']['age'] # 18

d.pop('name') # 'Tom'(删除指定key)
d.popitem() # 随机删除某一项
del d['name']  # 删除键值对
d.clear()  # 清空字典

# 按类型访问,可迭代
d.keys() # 列出所有键
d.values() # 列出所有值
d.items() # 列出所有键值对元组(k, v)

# 操作
d.setdefault('a', 3) # 插入一个键并给定默认值3,如不指定,则为None
d1.update(dict2) # 将字典dict2的键值对添加到字典dict
# 如果键存在,则返回其对应值;如果键不在字典中,则返回默认值
d.get('math', 100) # 100
d2 = d.copy() # 深拷贝,d变化不影响d2

d = {'a': 1, 'b': 2, 'c': 3}
max(d) # 'c'(最大的键)
min(d) # 'a'(最小的键)
len(d) # 3(字典的长度)
str(d) # "{'a': 1, 'b': 2, 'c': 3}"(字符串形式)
any(d) # True(只要一个键为True)
all(d) # True(所有键都为True)
sorted(d) # ['a', 'b', 'c'](所有键的列表排序)
2.2.7 集合
s = {} # 空集合
s = {'5元', '10元', '20元'} # 定义集合
s = set() # 空集合
s = set([1,2,3,4,5]) # {1, 2, 3, 4, 5}(使用列表定义)
s = {1, True, 'a'}
s = {1, 1, 1} # {1}(去重)
type(s) # set(类型检测)

s = {'a', 'b', 'c'}

# 判断是否有某个元素
'a' in s # True

# 添加元素
s.add(2) # {2, 'a', 'b', 'c'}
s.update([1,3,4]) # {1, 2, 3, 4, 'a', 'b', 'c'}

# 删除和清空元素
s.remove('a') # {'b', 'c'}(删除不存在的会报错)
s.discard('3') # 删除一个元素,无则忽略,不报错
s.clear() # set()(清空)

s1 = {1,2,3}
s2 = {2,3,4}

s1 & s2 # {2, 3}(交集)
s1.intersection(s2) # {2, 3}(交集)
s1.intersection_update(s2) # {2, 3}(交集,会覆盖s1)

s1 | s2  # {1, 2, 3, 4}(并集)
s1.union(s2) # {1, 2, 3, 4}(并集)

s1.difference(s2) # {1}(差集)
s1.difference_update(s2) # {1}(差集,会覆盖s1)

s1.symmetric_difference(s2) # {1, 4}(交集之外)

s1.isdisjoint(s2) # False(是否没有交集)
s1.issubset(s2) # False (s1是否是s2的子集)
s1.issuperset(s2) # False(s1是否是s2的超集,即s1是否包含s2中的所有元素)

2.3 NumPy

2.3.3 创建数据
import numpy as np

np.array([1, 2, 3])
np.array((1, 2, 3)) # 同上

np.array(((1, 2),(1, 2)))
np.array(([1, 2],[1, 2])) # 同上

np.arange(10) # 10个,不包括10,步长为1
np.arange(3, 10, 0.1) # 从3到9,步长为0.1
# 从2.0到3.0,生成均匀的5个值,不包括终值3.0
np.linspace(2.0, 3.0, num=5, endpoint=False)
 # 返回一个6×4的随机数组,浮点型
np.random.randn(6, 4)
# 指定范围、指定形状的数组,整型
np.random.randint(3, 7, size=(2, 4))
# 创建值为0的数组
np.zeros(6) # 6个浮点0.
np.zeros((5, 6), dtype=int) # 5×6整型0
np.ones(4) # 同上
np.empty(4) # 同上
 # 创建一份和目标结构相同的0值数组
np.zeros_like(np.arange(6))
np.ones_like(np.arange(6)) # 同上
np.empty_like(np.arange(6)) # 同上
2.3.4 数据类型
np.int64 # 有符号64位整型
np.float32 # 标准双精度浮点类型
np.complex # 由128位的浮点数组成的复数类型
np.bool # bool类型(True或False)
np.object # Python中的object类型
np.string # 固定长度的string类型
np.unicode # 固定长度的unicode类型
np.NaN # np.float的子类型
np.nan
2.3.5 数组信息
n.shape() # 数组的形状,返回值是一个元组
n.shape = (4, 1) # 改变形状
a = n.reshape((2,2)) # 改变原数组的形状,创建一个新数组
n.dtype # 数据类型
n.ndim # 维度数
n.size # 元素数
np.typeDict # np的所有数据类型
2.3.6 统计计算
np.array([10, 20, 30, 40])[:3] # 支持类似列表的切片
a = np.array([10, 20, 30, 40])
b = np.array([1, 2, 3, 4])
a+b # array([11, 22, 33, 44])(矩阵相加)
a-1 # array([9, 19, 29, 39])
4*np.sin(a)

# 以下是一些数学函数的例子,还支持非常多的数学函数
a.max() # 40
a.min() # 10
a.sum() # 100
a.std() # 11.180339887498949
a.all() # True
a.cumsum() # array([10, 30, 60, 100])
b.sum(axis=1) # 多维可以指定方向

2.5 Pandas生成数据

2.5.1 导入Pandas
import pandas as pd
import numpy as np
2.5.2 创建数据
df2 = pd.DataFrame({'A': 1.,
                    'B': pd.Timestamp('20130102'),
                    'C': pd.Series(1, index=list(range(4)), dtype='float32'),
                    'D': np.array([3] * 4, dtype='int32'),
                    'E': pd.Categorical(["test", "train", "test", "train"]),
                    'F': 'foo'})

s = pd.Series([14.34, 21.43, 5.08], name='gdp')

type(s) # pandas.core.series.Series
type(df) # pandas.core.frame.DataFrame
2.5.3 生成Series
s = pd.Series(data, index=index)

pd.Series(['a', 'b', 'c', 'd', 'e'])
pd.Series(('a', 'b', 'c', 'd', 'e'))

# 由索引分别为a、b、c、d、e的5个随机浮点数数组组成
s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
s.index # 查看索引
s = pd.Series(np.random.randn(5)) # 未指定索引

d = {'b': 1, 'a': 0, 'c': 2}
s = pd.Series(d)
pd.Series(5.)

2.5.4 生成DataFrame
df = pd.DataFrame(data=None, index=None, columns=None)

d = {'国家': ['中国', '美国', '日本'],
     '人口': [14.33, 3.29, 1.26]}
df = pd.DataFrame(d)

df = pd.DataFrame(d, index=['a', 'b', 'c'])

2. Series组成的字典
d = {'x': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
     'y': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
df = pd.DataFrame(d)

3. 字典组成的列表
# 定义一个字典列表
data = [{'x': 1, 'y': 2}, {'x': 3, 'y': 4, 'z': 5}]

# 生成DataFrame对象
pd.DataFrame(data)

4. Series生成
s = pd.Series(['a', 'b', 'c', 'd', 'e'])
pd.DataFrame(s)

# 从字典里生成
pd.DataFrame.from_dict({'国家': ['中国', '美国', '日本'],'人口': [13.97, 3.28, 1.26]})
# 从列表、元组、ndarray中生成
pd.DataFrame.from_records([('中国', '美国', '日本'), (13.97, 3.28, 1.26)])
# 列内容为一个字典
pd.json_normalize(df.col)
df.col.apply(pd.Series)

2.6 Pandas的数据类型

2.6.1 数据类型查看
df.dtypes # 各字段的数据类型
df.team.dtype
2.6.3 数据检测
pd.api.types.is_bool_dtype(s)
pd.api.types.is_categorical_dtype(s)
pd.api.types.is_datetime64_any_dtype(s)
pd.api.types.is_datetime64_ns_dtype(s)
pd.api.types.is_datetime64_dtype(s)
pd.api.types.is_float_dtype(s)
pd.api.types.is_int64_dtype(s)
pd.api.types.is_numeric_dtype(s)
pd.api.types.is_object_dtype(s)
pd.api.types.is_string_dtype(s)
pd.api.types.is_timedelta64_dtype(s)

第3章 Pandas数据读取与输出

3.1 数据读取

3.1.1 CSV文件
# 文件目录
pd.read_csv('data.csv') # 如果文件与代码文件在同一目录下
pd.read_csv('data/my/my.data') # CSV文件的扩展名不一定是.csv
# 使用URL
pd.read_csv('https://www.gairuo.com/file/data/dataset/GDP-China.csv')
3.1.2 Excel
# 返回DataFrame
pd.read_excel('team.xlsx') # 默认读取第一个标签页Sheet
pd.read_excel('path_to_file.xlsx', sheet_name='Sheet1') # 指定Sheet
# 从URL读取
pd.read_excel('https://www.gairuo.com/file/data/dataset/team.xlsx')
3.1.3 JSON
# data.json为同目录下的一个文件
pd.read_json('data.json')
3.1.4 HTML
dfs = pd.read_html('https://www.gairuo.com/p/pandas-io')
dfs[0] # 查看第一个# 读取网页文件,第一行为表头
dfs = pd.read_html('data.html', header=0)
# 第一列为索引
dfs = pd.read_html(url, index_col=0)
# id='table'的表格,注意这里仍然可能返回多个
dfs1 = pd.read_html(url, attrs={'id': 'table'})
3.1.5 剪贴板
# 复制上边的数据,然后直接赋值
cdf = pd.read_clipboard()
3.1.6 SQL
# 需要安装SQLAlchemy库
from sqlalchemy import create_engine
# 创建数据库对象,SQLite内存模式
engine = create_engine('sqlite:///:memory:')
# 取出表名为data的表数据
with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table('data', conn)
# 将数据写入
data.to_sql('data', engine)
# 大量写入
data.to_sql('data_chunked', engine, chunksize=1000)
# 使用SQL查询
pd.read_sql_query('SELECT * FROM data', engine)

3.2 读取CSV

3.2.2 数据内容
from io import StringIO
data = ('col1,col2,col3\n'
        'a,b,1\n'
        'a,b,2\n'
        'c,d,3')

pd.read_csv(StringIO(data))
pd.read_csv(StringIO(data), dtype=object)

from io import BytesIO
data = (b'word,length\n'
        b'Tr\xc3\xa4umen,7\n'
        b'Gr\xc3\xbc\xc3\x9fe,5')

pd.read_csv(BytesIO(data))
3.2.3 分隔符
# 数据分隔符默认是逗号,可以指定为其他符号
pd.read_csv(data, sep='\t') # 制表符分隔tab
pd.read_table(data) # read_table 默认是制表符分隔tab
pd.read_csv(data, sep='|') # 制表符分隔tab
pd.read_csv(data,sep="(?<!a)\|(?!1)", engine='python') # 使用正则表达式
3.2.4 表头
pd.read_csv(data, header=0) # 第一行
pd.read_csv(data, header=None) # 没有表头
pd.read_csv(data, header=[0,1,3]) # 多层索引MultiIndex
3.2.5 列名
pd.read_csv(data, names=['列1', '列2']) # 指定列名列表
pd.read_csv(data, names=['列1', '列2'], header=None)
3.2.6 索引
# 支持int、str、int序列、str序列、False,默认为None
pd.read_csv(data, index_col=False) # 不再使用首列作为索引
pd.read_csv(data, index_col=0) # 第几列是索引
pd.read_csv(data, index_col='年份') # 指定列名
pd.read_csv(data, index_col=['a','b']) # 多个索引
pd.read_csv(data, index_col=[0, 3]) # 按列索引指定多个索引

3.3 读取Excel

3.3.2 文件内容
# 字符串、字节、Excel文件、xlrd.Book实例、路径对象或者类似文件的对象
# 本地相对路径
pd.read_excel('data/data.xlsx') # 注意目录层级
# 本地绝对路径
pd.read_excel('/user/gairuo/data/data.xlsx')
# 使用URL
pd.read_excel('https://www.gairuo.com/file/data/dataset/team.xlsx')
3.3.3 表格
# 字符串、整型、列表、None,默认为0
pd.read_excel('tmp.xlsx', sheet_name=1) # 第二个sheet
pd.read_excel('tmp.xlsx', sheet_name='总结表') # 按sheet的名字

# 读取第一个、第二个、名为Sheet5的sheet,返回一个df组成的字典
dfs = pd.read_excel('tmp.xlsx', sheet_name=[0, 1, "Sheet5"])
dfs = pd.read_excel('tmp.xlsx', sheet_name=None) # 所有sheet
dfs['Sheet5'] # 读取时按sheet名
3.3.4 表头
# 整型、整型组成的列表,默认为 0
pd.read_excel('tmp.xlsx', header=None)  # 不设表头
pd.read_excel('tmp.xlsx', header=2)  # 第三行为表头
pd.read_excel('tmp.xlsx', header=[0, 1])  # 两层表头,多层索引
3.3.5 列名
# 序列,默认为None
pd.read_excel('tmp.xlsx', names=['姓名', '年龄', '成绩'])
pd.read_excel('tmp.xlsx', names=c_list) # 传入列表变量
# 没有表头,需要设置为None
pd.read_excel('tmp.xlsx', header=None, names=None)

3.4 数据输出

3.4.1 CSV
df.to_csv('data/done.csv') # 可以指定文件目录路径
df.to_csv('done.csv', index=False) # 不要索引

# 创建一个包含out.csv的压缩文件out.zip
compression_opts = dict(method='zip',
                        archive_name='out.csv')
df.to_csv('out.zip', index=False,
          compression=compression_opts)
3.4.2 Excel
# 指定sheet名,不要索引
df.to_excel('path_to_file.xlsx', sheet_name='Sheet1', index=False)
# 指定索引名,不合并单元格
df.to_excel('path_to_file.xlsx', index_label='label', merge_cells=False)

# 将多个df分不同sheet导入一个Excel文件中
with pd.ExcelWriter('path_to_file.xlsx') as writer:
    df1.to_excel(writer, sheet_name='Sheet1')
    df2.to_excel(writer, sheet_name='Sheet2')

# 指定操作引擎
df.to_excel('path_to_file.xlsx', sheet_name='Sheet1', engine='xlsxwriter')
# 在'engine'参数中设置ExcelWriter使用的引擎
writer = pd.ExcelWriter('path_to_file.xlsx', engine='xlsxwriter')
df.to_excel(writer)
writer.save()

# 设置系统引擎
from pandas import options  # noqa: E402
options.io.excel.xlsx.writer = 'xlsxwriter'
df.to_excel('path_to_file.xlsx', sheet_name='Sheet1')
3.4.3 HTML
print(df.to_html(columns=[0])) # 输出指定列
print(df.to_html(bold_rows=False)) # 表头不加粗
# 表格指定样式,支持多个
print(df.to_html(classes=['class1', 'class2']))
3.4.4 数据库(SQL)
# 需要安装SQLAlchemy库
from sqlalchemy import create_engine
# 创建数据库对象,SQLite内存模式
engine = create_engine('sqlite:///:memory:')
# 取出表名为data的表数据
with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table('data', conn)

# 将数据写入
data.to_sql('data', engine)
# 大量写入
data.to_sql('data_chunked', engine, chunksize=1000)
# 使用SQL查询
pd.read_sql_query('SELECT * FROM data', engine)
3.4.5 Markdown
print(cdf.to_markdown())

第4章 Pandas基础操作

4.1 索引操作

4.1.2 建立索引
df = pd.read_excel(data, index_col='name') # 将索引设置为name
df = pd.read_excel(data) # 读取数据不设索引
df.set_index('name') # 设置索引
df.set_index(['name', 'team']) # 设置两层索引
df.set_index([df.name.str[0],'name']) # 将姓名的第一个字母和姓名设置为索引
df = df.set_index('name') # 建立索引并重写覆盖
df.set_index('name', inplace=True) # 同上,使索引生效
s = pd.Series([i for i in range(100)])
df.set_index(s) # 指定一个索引
df.set_index([s, 'name']) # 同时指定索引和现有字段
df.set_index([s, s**2]) # 计算索引
df.set_index('month', drop=False) # 保留原列
df.set_index('month', append=True) # 保留原来的索引
4.1.3 重置索引
df.reset_index() # 清除索引
df.set_index('month').reset_index() # 相当于什么也没做
# 删除原索引,month列没了
df.set_index('month').reset_index(drop=True)
df2.reset_index(inplace=True) # 覆盖使生效
# year一级索引取消
df.set_index(['month', 'year']).reset_index(level=1)
df2.reset_index(level='class') # 同上,使用层级索引名
df.reset_index(level='class', col_level=1) # 列索引
# 不存在层级名称的填入指定名称
df.reset_index(level='class', col_level=1, col_fill='species')
4.1.4 索引类型
pd.RangeIndex(1,100,2)
pd.Int64Index([1,2,3,-4], name='num')
pd.UInt64Index([1,2,3,4])
pd.Float64Index([1.2,2.3,3,4])
pd.CategoricalIndex(['a', 'b', 'a', 'b'])
    dtype='category')
pd.interval_range(start=0, end=5)
arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']]
pd.MultiIndex.from_arrays(arrays, names=('number', 'color'))
# 从一个日期连续到另一个日期
pd.date_range(start='1/1/2018', end='1/08/2018')
# 指定开始时间和周期
pd.date_range(start='1/1/2018', periods=8)
# 以月为周期
pd.period_range(start='2017-01-01', end='2018-01-01', freq='M')
# 周期嵌套
pd.period_range(start=pd.Period('2017Q1', freq='Q'),
                end=pd.Period('2017Q2', freq='Q'), freq='M')
pd.TimedeltaIndex(data =['06:05:01.000030', '+23:59:59.999999',
                         '22 day 2 min 3us 10ns', '+23:29:59.999999',
                         '+12:19:59.999999'])
# 使用datetime
pd.TimedeltaIndex(['1 days', '1 days, 00:00:05',
                   np.timedelta64(2, 'D'),
                   datetime.timedelta(days=2, seconds=2)])
t = pd.period_range('2020-5-1 10:00:05', periods=8, freq='S')
pd.PeriodIndex(t,freq='S')
4.1.5 索引对象
pd.Index([1, 2, 3])
pd.Index(list('abc'))
# 可以用name指定一个索引名称
pd.Index(['e', 'd', 'a', 'b'], name='something')
df.index
df.columns
4.1.6 索引的属性
# 常用属性
df.index.name # 名称
df.index.array # array数组
df.index.dtype # 数据类型
df.index.shape # 形状
df.index.size # 元素数量
df.index.values # array数组
df.index.empty # 是否为空
df.index.is_unique # 是否不重复
df.index.names # 名称列表
df.index.is_all_dates # 是否全是日期时间
df.index.has_duplicates # 是否有重复值
df.index.values # 索引的值array
4.1.7 索引的操作
df.index.astype('int64') # 转换类型
df.index.isin() # 是否存在,见下方示例
df.index.rename('number') # 修改索引名称
df.index.nunique() # 不重复值的数量
df.index.sort_values(ascending=False,) # 排序,倒序
df.index.map(lambda x:x+'_') # map函数处理
df.index.str.replace('_', '') # str替换
df.index.str.split('_') # 分隔
df.index.to_list() # 转为列表
df.index.to_frame(index=False, name='a') # 转成DataFrame
df.index.to_series() # 转为series
df.index.to_numpy() # 转为numpy
df.index.unique() # 去重
df.index.value_counts() # 去重及计数
df.index.where(df.index=='a') # 筛选
df.index.rename('grade', inplace=False) # 重命名索引
df.index.rename(['species', 'year']) # 多层,重命名索引
df.index.max() # 最大值
df.index.argmax() # 最大索引值
df.index.any()
df.index.all()
df.index.T # 转置,在多层索引里很有用
df.index.append(pd.Index([4,5])) # 追加
df.index.repeat(2) # 重复几次
df.index.inferred_type # 推测数据类型
df.index.hasnans # 有没有空值
df.index.is_monotonic_decreasing # 是否单调递减
df.index.is_monotonic # 是否有单调性
df.index.is_monotonic_increasing # 是否单调递增
df.index.nbytes # 基础数据中的字节数
df.index.ndim # 维度数,维数
df.index.nlevels # 索引层级数,通常为1
df.index.min() # 最小值
df.index.argmin() # 最小索引值
df.index.argsort() # 顺序值组成的数组
df.index.asof(2) # 返回最近的索引
# 索引类型转换
df.index.astype('int64', copy=True) # 深拷贝
# 拷贝
df.index.copy(name='new', deep=True, dtype='int64')
df.index.delete(1) # 删除指定位置
# 对比不同
df.index.difference(pd.Index([1,2,4]), sort=False)
df.index.drop('a', errors='ignore') # 删除
df.index.drop_duplicates(keep='first') # 去重值
df.index.droplevel(0) # 删除层级
df.index.dropna(how='all') # 删除空值
df.index.duplicated(keep='first') # 重复值在结果数组中为True
df.index.equals(df.index) # 与另一个索引对象是否相同
df.index.factorize() # 分解成(array:0-n, Index)
df.index.fillna(0, {0:'nan'}) # 填充空值
# 字符列表,把name值加在第一位,每个值加10
df.index.format(name=True, formatter=lambda x:x+10)
# 返回一个array,指定值的索引位数组,不在的为-1
df.index.get_indexer([2,9])
# 获取指定层级Index对象
df.index.get_level_values(0)
# 指定索引的位置,见示例
df.index.get_loc('b')
df.index.insert(2, 'f') # 在索引位2插入f
df.index.intersection(df.index) # 交集
df.index.is_(df.index) # 类似is检查
df.index.is_categorical() # 是否分类数据
df.index.is_type_compatible(df.index) # 类型是否兼容

df.index.is_type_compatible(1) # 类型是否兼容
df.index.isna() # array是否为空
df.index.isnull() # array是否缺失值
df.index.join(df.index, how='left') # 连接
df.index.notna() # 是否不存在的值
df.index.notnull() # 是否不存在的值
df.index.ravel() # 展平值的ndarray
df.index.reindex(['a','b']) # 新索引 (Index,array:0-n)
df.index.searchsorted('f') # 如果插入这个值,排序后在哪个索引位
df.index.searchsorted([0, 4]) # array([0, 3]) 多个
df.index.set_names('quarter') # 设置索引名称
df.index.set_names('species', level=0)
df.index.set_names(['kind', 'year'], inplace=True)
df.index.shift(10, freq='D') # 日期索引向前移动10天
idx1.symmetric_difference(idx2) # 两个索引不同的内容
idx1.union(idx2) # 拼接

df.add_prefix('t_') # 表头加前缀
df.add_suffix('_d') # 表头加后缀
df.first_valid_index() # 第一个有值的索引
df.last_valid_index() # 最后一个有值的索引
4.1.8 索引重命名
s.rename_axis("student_name") # 索引重命名
df.rename_axis(["dow", "hr"]) # 多层索引修改索引名
df.rename_axis('info', axis="columns") # 修改行索引名
# 修改多层列索引名
df.rename_axis(index={'a': 'A', 'b': 'B'})
# 修改多层列索引名
df.rename_axis(columns={'name': 's_name', 'b': 'B'})
df.rename_axis(columns=str.upper) # 行索引名变大写
4.1.9 修改索引内容
df.rename(columns={"A": "a", "B": "c"})
df.rename(str.lower, axis='columns')
# 修改行索引
df.rename(index={0: "x", 1: "y", 2: "z"})
df.rename({1: 2, 2: 4}, axis='index')
# 修改数据类型
df.rename(index=str)
# 重新修改索引
replacements = {l1:l2 for l1, l2 in zip(list1, list2)}
df.rename(replacements)
# 列名加前缀
df.rename(lambda x:'t_' + x, axis=1)
# 利用iter()函数的next特性修改
df.rename(lambda x, y=iter('abcdef'): next(y), axis=1)
# 修改列名,用解包形式生成新旧字段字典
df.rename(columns=dict(zip(df, list('abcd'))))
# 修改索引
df.set_axis(['a', 'b', 'c'], axis='index')
# 修改列名
df.set_axis(list('abcd'), axis=1)
# 使修改生效
df.set_axis(['a', 'b'], axis='columns', inplace=True)
# 传入索引内容
df.set_axis(pd.Index(list('abcde')), axis=0)

4.2 数据的信息

4.2.1 查看样本
df = pd.read_excel('https://www.gairuo.com/file/data/dataset/team.xlsx')
s = df.Q1 # 取其中一列,形成Series
df.head() # 查看前5条数据
df.head(10) # 查看前10条数据
s.tail() # 查看后5条数据
df.tail(10) # 查看后10条数据
df.sample() # 随机查看一条数据
s.sample(3) # 随机查看3条数据
4.2.2 数据形状
df.shape
s.shape
4.2.3 基础信息
df.info
4.2.4 数据类型
df.dtypes
s.dtype
4.2.5 行列索引内容
df.axes
s.axes
4.2.6 其他信息
# 索引对象
df.index
# 列索引,Series不支持
df.columns
df.values # array(<所有值的列表矩阵>)
df.ndim # 2 维度数
df.size # 600行×列的总数,就是总共有多少数据
# 是否为空,注意,有空值不认为是空
df.empty # False
# Series的索引,DataFrame的列名
df.keys()
s.name # 'Q1'
s.array # 值组成的数组 <PandasArray>
s.dtype # 类型,dtype('int64')
s.hasnans # False

4.3 统计计算

4.3.1 描述统计
df.describe()
pd.Series(['a', 'b', 'c', 'c']).describe()
(pd.Series(pd.date_range('2000-01-01', '2000-05-01'))
 .describe(datetime_is_numeric=True))

df.describe(percentiles=[.05, .25, .75, .95])
df.describe(include=[np.object, np.number]) # 指定类型
df.describe(exclude =[np.object]) # 排除类型
4.3.2 数学统计
df.mean()
type(df.mean())
df.Q1.mean()
s.mean()
df.mean(axis='columns')
df.mean(axis=1) 
df.mean(1) 
# 创建name为索引,计算每行平均值,只看前5条
df.set_index('name').mean(1).head()
4.3.3 统计函数
df.mean() # 返回所有列的均值
df.mean(1) # 返回所有行的均值,下同
df.corr() # 返回列与列之间的相关系数
df.count() # 返回每一列中的非空值的个数
df.max() # 返回每一列的最大值
df.min() # 返回每一列的最小值
df.abs() # 绝对值
df.median() # 返回每一列的中位数
df.std() # 返回每一列的标准差,贝塞尔校正的样本标准偏差
df.var() # 无偏方差
df.sem() # 平均值的标准误差
df.mode() # 众数
df.prod() # 连乘
df.mad() # 平均绝对偏差
df.cumprod() # 累积连乘,累乘
df.cumsum(axis=0) # 累积连加,累加
df.nunique() # 去重数量,不同值的量
df.idxmax() # 每列最大值的索引名
df.idxmin() # 每列最小值的索引名
df.cummax() # 累积最大值
df.cummin() # 累积最小值
df.skew() # 样本偏度(第三阶)
df.kurt() # 样本峰度(第四阶)
df.quantile() # 样本分位数(不同 % 的值)
# 很多支持指定行列(默认是axis=0列)等参数
df.mean(1) # 按行计算
df.sum(0, skipna=False) # 不除缺失数据
df.sum(level='blooded') # 索引级别
df.sum(level=0)
# 执行加法操作所需的最小有效值数
df.sum(min_count=1)
4.3.4 非统计计算
df.all() # 返回所有列all()值的Series
df.any()

# 四舍五入
df.round(2) # 指定字段指定保留小数位,如有
df.round({'Q1': 2, 'Q2': 0})
df.round(-1) # 保留10位

# 每个列的去重值的数量
df.nunique()
s.nunique() # 本列的去重值

# 真假检测
df.isna() # 值的真假值替换
df.notna() # 与上相反
df + 1 # 等运算
df.add() # 加
df.sub() # 减
df.mul() # 乘
df.div() # 除
df.mod() # 模,除后的余数
df.pow() # 指数幂
df.dot(df2) # 矩阵运算

# 不重复的值及数量
s.value_counts()
s.value_counts(normalize=True) # 重复值的频率
s.value_counts(sort=False) # 不按频率排序

s.unique() # 去重的值 array
s.is_unique # 是否有重复

# 最大最小值
s.nlargest() # 最大的前5个
s.nlargest(15) # 最大的前15个
s.nsmallest() # 最小的前5个
s.nsmallest(15) # 最小的前15个

s.pct_change() # 计算与前一行的变化百分比
s.pct_change(periods=2) # 前两行
s1.cov(s2) # 两个序列的协方差

4.4 位置计算

4.4.1 位置差值diff()
pd.Series([9, 4, 6, 7, 9])
# 只筛选4个季度的5条数据
df.loc[:5,'Q1':'Q4'].diff(1, axis=1)
4.4.2 位置移动shift()
# 整体下移一行,最顶的一行为NaN
df.shift()
df.shift(3) # 移三行
# 整体上移一行,最底的一行为NaN
df.Q1.head().shift(-1)
# 向右移动一位
df.shift(axis=1)
df.shift(3, axis=1) # 移三位
# 向左移动一位
df.shift(-1, axis=1)
# 实现了df.Q1.diff()
df.Q1 - df.Q1.shift()
4.4.3 位置序号rank()
# 排名,将值变了序号
df.head().rank()
df.head().rank(pct=True)

4.5 数据选择

4.5.1 选择列
df['name'] 
df.name
df.Q1
type(df.Q1)
4.5.2 切片[]
df[:2] # 前两行数据
df[4:10]
df[:] # 所有数据,一般不这么用
df[:10:2] # 按步长取
s[::-1] # 反转顺序
df[2] # 报错!
df[['name','Q4']]
df[['name']] # 选择一列,返回DataFrame
df['name'] # 只有一列,返回Series
4.5.3 按轴标签.loc
# 代表索引,如果是字符,需要加引号
df.loc[0] # 选择索引为0的行
df.loc[8]
# 索引为name
df.set_index('name').loc['Ben']
df.loc[[0,5,10]] # 指定索引为0,5,10的行
df.set_index('name').loc[['Eli', 'Ben']] # 两位学生,索引是name
df.loc[[False, True]*50] # 为真的列显示,隔一个显示一个
df.loc[0:5] # 索引切片,代表0~5行,包括5
df.loc['2010':'2014'] # 如果索引是时间,可以用字符查询
df.loc[:] # 所有
df.loc[0:5, ['name', 'Q2']]
df.loc[0:9, ['Q1', 'Q2']] # 前10行,Q1和Q2两列
df.loc[:, ['Q1', 'Q2']] # 所有行,Q1和Q2两列
df.loc[:10, 'Q1':] # 0~10行,Q1后边的所有列
df.loc[:, :] # 所有内容
4.5.4 按数字索引.iloc
df.iloc[:3] # 前三行
s.iloc[:3] # 序列中的前三个
df.iloc[:] # 所有数据
df.iloc[2:20:3] # 步长为3
df.iloc[:3, [0,1]] # 前两列
df.iloc[:3, :] # 所有列
df.iloc[:3, :-2] # 从右往左第三列以左的所有列
4.5.5 取具体值.at/.iat
df.at[4, 'Q1'] # 65
df.set_index('name').at['Ben', 'Q1'] # 21 索引是name
df.at[0, 'name'] # 'Liver'
df.loc[0].at['name'] # 'Liver'
# 指定列的值对应其他列的值
df.set_index('name').at['Eorge', 'team'] # 'C'
df.set_index('name').team.at['Eorge'] # 'C'
# 指定列的对应索引的值
df.team.at[3] # 'C'
df.iat[4, 2] # 65
df.loc[0].iat[1] # 'E'
4.5.6 获取数据.get
df.get('name', 0) # 是name列
df.get('nameXXX', 0) # 0,返回默认值
s.get(3, 0) # 93,Series传索引返回具体值
df.name.get(99, 0) # 'Ben'
4.5.7 数据截取.truncate
df.truncate(before=2, after=4)
4.5.8 索引选择器
df.loc[pd.IndexSlice[:, ['Q1', 'Q2']]]
idx = pd.IndexSlice
df.loc[idx[:, ['Q1', 'Q2']]]
df.loc[idx[:, 'Q1':'Q4'], :] # 多索引
# 创建复杂条件选择器
selected = df.loc[(df.team=='A') & (df.Q1>90)]
idxs = pd.IndexSlice[selected.index, 'name']
# 应用选择器
df.loc[idxs]
# 选择这部分区域加样式
df.style.applymap(style_fun, subset=idxs)

第5章 Pandas高级操作

5.1 复杂查询

5.1.1 逻辑运算
# Q1成绩大于36
df.Q1 > 36
# 索引等于1
df.index == 1
# df.loc[:,'Q1':'Q4']部分只取数字部分,否则会因字符无大于运算而报错
df.loc[:,'Q1':'Q4'] > 60
# Q1成绩不小于60分,并且是C组成员
~(df.Q1 < 60) & (df['team'] == 'C')
5.1.2 逻辑筛选数据
df[df['Q1'] == 8] # Q1等于8
df[~(df['Q1'] == 8)] # 不等于8
df[df.name == 'Ben'] # 姓名为Ben
df[df.Q1 > df.Q2]
# 表达式与切片一致
df.loc[df['Q1'] > 90, 'Q1':]  # Q1大于90,只显示Q1
df.loc[(df.Q1 > 80) & (df.Q2 < 15)] # and关系
df.loc[(df.Q1 > 90) | (df.Q2 < 90)] # or关系
df.loc[df['Q1'] == 8] # 等于8
df.loc[df.Q1 == 8] # 等于8
df.loc[df['Q1'] > 90, 'Q1':] # Q1大于90,显示Q1及其后所有列
# Q1、Q2成绩全为超过80分的
df[(df.loc[:,['Q1','Q2']] > 80).all(1)]
# Q1、Q2成绩至少有一个超过80分的
df[(df.loc[:,['Q1','Q2']] > 80).any(1)]
5.1.3 函数筛选
# 查询最大索引的值
df.Q1[lambda s: max(s.index)] # 值为21
# 计算最大值
max(df.Q1.index) # 99
df.Q1[df.index==99]
df[lambda df: df['Q1'] == 8] # Q1为8的
df.loc[lambda df: df.Q1 == 8, 'Q1':'Q2'] # Q1为8的,显示 Q1、Q2
df.loc[:, lambda df: df.columns.str.len()==4] # 由真假值组成的序列
df.loc[:, lambda df: [i for i in df.columns if 'Q' in i]] # 列名列表
df.iloc[:3, lambda df: df.columns.str.len()==2] # 由真假值组成的序列
5.1.4 比较函数
# 以下相当于 df[df.Q1 == 60]
df[df.Q1.eq(60)]
df.ne() # 不等于 !=
df.le() # 小于等于 <=
df.lt() # 小于 <
df.ge() # 大于等于 >=
df.gt() # 大于 >
df[df.Q1.ne(89)] # Q1不等于89
df.loc[df.Q1.gt(90) & df.Q2.lt(90)] # and关系,Q1>90,Q2<90
df[df.team.isin(['A','B'])] # 包含A、B两组的
df[df.isin({'team': ['C', 'D'], 'Q1':[36,93]})] # 复杂查询,其他值为NaN
5.1.5 查询df.query()
df.query('Q1 > Q2 > 90') # 直接写类型SQL where语句
df.query('Q1 + Q2 > 180')
df.query('Q1 == Q2')
df.query('(Q1<50) & (Q2>40) and (Q3>90)')
df.query('Q1 > Q2 > Q3 > Q4')
df.query('team != "C"')
df.query('team not in ("E","A","B")')
# 对于名称中带有空格的列,可以使用反引号引起来
df.query('B == `team name`')
# 支持传入变量,如大于平均分40分的
a = df.Q1.mean()
df.query('Q1 > @a+40')
df.query('Q1 > `Q2`+@a')
# df.eval()用法与df.query类似
df[df.eval("Q1 > 90 > Q3 > 10")]
df[df.eval("Q1 > `Q2`+@a")]
5.1.6 筛选df.filter()
df.filter(items=['Q1', 'Q2']) # 选择两列
df.filter(regex='Q', axis=1) # 列名包含Q的列
df.filter(regex='e$', axis=1) # 以e结尾的列
df.filter(regex='1$', axis=0) # 正则,索引名以1结尾
df.filter(like='2', axis=0) # 索引中有2的
# 索引中以2开头、列名有Q的
df.filter(regex='^2', axis=0).filter(like='Q', axis=1)
5.1.7 按数据类型查询
df.select_dtypes(include=['float64']) # 选择float64型数据
df.select_dtypes(include='bool')
df.select_dtypes(include=['number']) # 只取数字型
df.select_dtypes(exclude=['int']) # 排除int类型
df.select_dtypes(exclude=['datetime64'])

5.2 数据类型转换

# 对所有字段指定统一类型
df = pd.DataFrame(data, dtype='float32')
# 对每个字段分别指定
df = pd.read_excel(data, dtype={'team': 'string', 'Q1': 'int32'})
5.2.1 推断类型
# 自动转换合适的数据类型
df.infer_objects() # 推断后的DataFrame
df.infer_objects().dtypes
5.2.2 指定类型
# 按大体类型推定
m = ['1', 2, 3]
s = pd.to_numeric(s) # 转成数字
pd.to_datetime(m) # 转成时间
pd.to_timedelta(m) # 转成时间差
pd.to_datetime(m, errors='coerce') # 错误处理
pd.to_numeric(m, errors='ignore')
pd.to_numeric(m errors='coerce').fillna(0) # 兜底填充
pd.to_datetime(df[['year', 'month', 'day']]) # 组合成日期
# 最低期望
pd.to_numeric(m, downcast='integer') # 至少为有符号int数据类型
pd.to_numeric(m, downcast='signed') # 同上
pd.to_numeric(m, downcast='unsigned') # 至少为无符号int数据类型
pd.to_numeric(m, downcast='float') # 至少为float浮点类型
df = df.select_dtypes(include='number')
df.apply(pd.to_numeric)
5.2.3 类型转换astype()
df.Q1.astype('int32').dtypes
# dtype('int32')
df.astype({'Q1': 'int32','Q2': 'int32'}).dtypes
df.index.astype('int64') # 索引类型转换
df.astype('int32') # 所有数据转换为int32
df.astype({'col1': 'int32'}) # 指定字段转指定类型
s.astype('int64')
s.astype('int64', copy=False) # 不与原数据关联
s.astype(np.uint8)
df['name'].astype('object')
data['Q4'].astype('float')
s.astype('datetime64[ns]')
data['状态'].astype('bool')
data.rate.apply(lambda x: x.replace('%', '')).astype('float')/100

5.3 数据排序

5.3.1 索引排序
# 索引降序
df.sort_index(ascending=False)
# 在列索引方向上排序
df.sort_index(axis=1, ascending=False)
s.sort_index() # 升序排列
df.sort_index() # df也是按索引进行排序
df.team.sort_index()
s.sort_index(ascending=False) # 降序排列
s.sort_index(inplace=True) # 排序后生效,改变原数据
# 索引重新0-(n-1)排,很有用,可以得到它的排序号
s.sort_index(ignore_index=True)
s.sort_index(na_position='first') # 空值在前,另'last'表示空值在后
s.sort_index(level=1) # 如果多层,排一级
s.sort_index(level=1, sort_remaining=False) # 这层不排
# 行索引排序,表头排序
df.sort_index(axis=1) # 会把列按列名顺序排列
df = pd.DataFrame({
    'A': [1,2,4],
    'B': [3,5,6]
}, index=['a', 'b', 'c'])
5.3.2 数值排序
df.Q1.sort_values()
df.sort_values('Q4')
df.sort_values(by=['team', 'name'], ascending=[True, False])
s.sort_values(ascending=False) # 降序
s.sort_values(inplace=True) # 修改生效
s.sort_values(na_position='first') # 空值在前
# df按指定字段排列
df.sort_values(by=['team'])
df.sort_values('Q1')
# 按多个字段,先排team,在同team内再看Q1
df.sort_values(by=['team', 'Q1'])
# 全降序
df.sort_values(by=['team', 'Q1'], ascending=False)
# 对应指定team升Q1降
df.sort_values(by=['team', 'Q1'], ascending=[True, False])
# 索引重新0-(n-1)排
df.sort_values('team', ignore_index=True)
5.3.3 混合排序
df.set_index('name', inplace=True) # 设置name为索引
df.index.names = ['s_name'] # 给索引起名
df.sort_values(by=['s_name', 'team']) # 排序
# 设置索引,按team排序,再按索引排序
df.set_index('name').sort_values('team').sort_index()
# 按姓名排序后取出排名后的索引列表
df.name.sort_values().index
5.3.4 按值大小排序
# 先按Q1最小在前,如果相同,Q2小的在前
df.nsmallest(5, ['Q1', 'Q2'])
s.nsmallest(3) # 最小的3个
s.nlargest(3) # 最大的3个
df.nlargest(3, 'Q1')
df.nlargest(5, ['Q1', 'Q2'])
df.nsmallest(5, ['Q1', 'Q2'])

5.4 添加修改

5.4.1 修改数值
df.iloc[0,0] # 查询值
df.iloc[0,0] = 'Lily' # 修改值
df.iloc[0,0] # 查看结果
# 将小于60分的成绩修改为60
df[df.Q1 < 60] = 60
# 筛选数据
df.loc[1:3, 'Q1':'Q2']

5.4.2 替换数据
s.replace(0, 5) # 将列数据中的0换为5
df.replace(0, 5) # 将数据中的所有0换为5
df.replace([0, 1, 2, 3], 4) # 将0~3全换成4
df.replace([0, 1, 2, 3], [4, 3, 2, 1]) # 对应修改
s.replace([1, 2], method='bfill') # 向下填充
df.replace({0: 10, 1: 100}) # 字典对应修改
df.replace({'Q1': 0, 'Q2': 5}, 100) # 将指定字段的指定值修改为100
df.replace({'Q1': {0: 100, 4: 400}}) # 将指定列里的指定值替换为另一个指定的值
# 使用正则表达式
df.replace(to_replace=r'^ba.$', value='new', regex=True)
df.replace({'A': r'^ba.$'}, {'A': 'new'}, regex=True)
df.replace(regex={r'^ba.$': 'new', 'foo': 'xyz'})
df.replace(regex=[r'^ba.$', 'foo'], value='new')
5.4.3 填充空值
df.fillna(0) # 将空值全修改为0
df.fillna(method='ffill') # 将空值都修改为其前一个值
values = {'A': 0, 'B': 1, 'C': 2, 'D': 3}
df.fillna(value=values) # 为各列填充不同的值
df.fillna(value=values, limit=1) # 只替换第一个
5.4.4 修改索引名
df.rename(columns={'team':'class'})
df.rename(columns={"Q1": "a", "Q2": "b"}) # 对表头进行修改
df.rename(index={0: "x", 1: "y", 2: "z"}) # 对索引进行修改
df.rename(index=str) # 对类型进行修改
df.rename(str.lower, axis='columns') # 传索引类型
df.rename({1: 2, 2: 4}, axis='index')

# 对索引名进行修改
s.rename_axis("animal")
df.rename_axis("animal") # 默认是列索引
df.rename_axis("limbs", axis="columns") # 指定行索引
# 索引为多层索引时可以将type修改为class
df.rename_axis(index={'type': 'class'})

# 可以用set_axis进行设置修改
s.set_axis(['a', 'b', 'c'], axis=0)
df.set_axis(['I', 'II'], axis='columns')
df.set_axis(['i', 'ii'], axis='columns', inplace=True)
5.4.5 增加列
# 四个季度的成绩相加为总成绩
df['total'] = df.Q1 + df.Q2 + df.Q3 + df.Q4
df['total'] = df.sum(1) # 与以上代码效果相同
df['foo'] = 100 # 增加一列foo,所有值都是100
df['foo'] = df.Q1 + df.Q2 # 新列为两列相加
df['foo'] = df['Q1'] + df['Q2'] # 同上
# 把所有为数字的值加起来
df['total'] = df.select_dtypes(include=['int']).sum(1)
df['total'] = df.loc[:,'Q1':'Q4'].apply(lambda x: sum(x), axis='columns')
df.loc[:, 'Q10'] = '我是新来的' # 也可以
# 增加一列并赋值,不满足条件的为NaN
df.loc[df.num >= 60, '成绩'] = '合格'
df.loc[df.num < 60, '成绩'] = '不合格'
5.4.6 插入列df.insert()
# 在第三列的位置上插入新列total列,值为每行的总成绩
df.insert(2, 'total', df.sum(1))
5.4.7 指定列df.assign()
# 增加total列
df.assign(total=df.sum(1))
# 增加两列
df.assign(total=df.sum(1), Q=100)
df.assign(total=df.sum(1)).assign(Q=100) # 效果同上
# 使用了链式方法
(
    df.assign(total=df.sum(1)) # 总成绩
    .assign(Q=100) # 目标满分值
    .assign(name_len=df.name.str.len()) # 姓名长度
    .assign(avg=df.mean(1)) # 平均值
    .assign(avg2=lambda d: d.total/4) # 平均值2
)
df.assign(Q5=[100]*100) # 新增加一列Q5
df = df.assign(Q5=[100]*100) # 赋值生效
df.assign(Q6=df.Q2/df.Q1) # 计算并增加Q6
df.assign(Q7=lambda d: d.Q1 * 9 / 5 + 32) # 使用lambda
# 添加一列,值为表达式结果:True或False
df.assign(tag=df.Q1>df.Q2)
# 比较计算,True为1,False为0
df.assign(tag=(df.Q1>df.Q2).astype(int))
# 映射文案
df.assign(tag=(df.Q1>60).map({True:'及格',False:'不及格'}))
# 增加多个
df.assign(Q8=lambda d: d.Q1*5,
         Q9=lambda d: d.Q8+1) # Q8没有生效,不能直接用df.Q8
5.4.8 执行表达式df.eval()
# 传入求总分表达式
df.eval('total = Q1+Q3+Q3+Q4')
df['C1'] = df.eval('Q2 + Q3')
df.eval('C2 = Q2 + Q3') # 计算
a = df.Q1.mean()
df.eval("C3 = `Q3`+@a") # 使用变量
df.eval("C3 = Q2 > (`Q3`+@a)") # 加一个布尔值
df.eval('C4 = name + team', inplace=True) # 立即生效
5.4.9 增加行
# 新增索引为100的数据
df.loc[100] = ['tom', 'A', 88, 88, 88, 88]
df.loc[101]={'Q1':88,'Q2':99} # 指定列,无数据列值为NaN
df.loc[df.shape[0]+1] = {'Q1':88,'Q2':99} # 自动增加索引
df.loc[len(df)+1] = {'Q1':88,'Q2':99}
# 批量操作,可以使用迭代
rows = [[1,2],[3,4],[5,6]]
for row in rows:
    df.loc[len(df)] = row
5.4.10 追加合并
df = pd.DataFrame([[1, 2], [3, 4]], columns=list('AB'))
df2 = pd.DataFrame([[5, 6], [7, 8]], columns=list('AB'))
df.append(df2)
s1 = pd.Series(['a', 'b'])
s2 = pd.Series(['c', 'd'])
pd.concat([s1, s2])
pd.concat([s1, s2], ignore_index=True) # 索引重新编

# 原数索引不变,增加一个一层索引(keys里的内容),变成多层索引
pd.concat([s1, s2], keys=['s1', 's2'])
pd.concat([s1, s2], keys=['s1', 's2'],
          names=['Series name', 'Row ID'])
# df同理
pd.concat([df1, df2])
pd.concat([df1, df3], sort=False)
pd.concat([df1, df3], join="inner") # 只连相同列
pd.concat([df1, df4], axis=1) # 连接列
5.4.11 删除
s.pop(3)
5.4.12 删除空值
df.dropna() # 一行中有一个缺失值就删除
df.dropna(axis='columns') # 只保留全有值的列
df.dropna(how='all') # 行或列全没值才删除
df.dropna(thresh=2) # 至少有两个空值时才删除
df.dropna(inplace=True) # 删除并使替换生效

5.5 高级过滤

# 只保留数字类型列
df = df.select_dtypes(include='number')
5.5.1 df.where()
# 数值大于70
df.where(df > 70)
# Q1列大于50
df.where(lambda d: d.Q1>50)
# 传入布尔值Series,前三个为真
df.Q1.where(pd.Series([True]*3))
# 大于等于60分的显示成绩,小于的显示“不及格”
df.where(df>=60, '不及格')
# c 定义一个数是否为偶数的表达式
c = df%2 == 0
# 传入c, 为偶数时显示原值减去20后的相反数
df.where(~c, -(df-20))
5.5.2 np.where()
# 小于60分为不及格
np.where(df>=60, '合格', '不合格')
# 让df.where()中的条件为假,从而应用np.where()的计算结果
df.where(df==9999999, np.where(df>=60, '合格', '不合格'))
(
    df.assign(avg=df.mean(1)) # 计算一个平均数
    # 通过np.where()及判断平均分是否及格
    .assign(及格=lambda d: np.where(d.avg>=60, '是', '否'))
)
5.5.3 df.mask()
# 符合条件的为NaN
df.mask(s > 80)
# 对满足条件的位置指定填充值
df.Q1.mask(s > 80, '优秀')
# 返回布尔序列,符合条件的行值为True
(df.where((df.team=='A') & (df.Q1>60)) == df).Q1
# 返回布尔序列,符合条件的行值为False
(df.mask((df.team=='A') & (df.Q1>60)) == df).Q1
5.5.4 df.lookup()
# 行列相同数量,返回一个array
df.lookup([1,3,4], ['Q1','Q2','Q3']) # array([36, 96, 61])
df.lookup([1], ['Q1']) # array([36])

5.6 数据迭代

5.6.1 迭代Series
# 迭代指定的列
for i in df.name:
    print(i)
# 迭代索引和指定的两列
for i,n,q in zip(df.index, df.name, df.Q1):
    print(i, n, q)
5.6.2 df.iterrows()
# 迭代,使用name、Q1数据
for index, row in df.iterrows():
    print(index, row['name'], row.Q1)
5.6.3 df.itertuples()
for row in df.itertuples():
    print(row)

# 不包含索引数据
for row in df.itertuples(index=False):
    print(row)
# 自定义name
for row in df.itertuples(index=False, name='Gairuo'): # namedtuples
    print(row)

# 使用数据
for row in df.itertuples():
    print(row.Index, row.name)
5.6.4 df.items()
# Series取前三个
for label, ser in df.items():
    print(label)
    print(ser[:3], end='\n\n')
5.6.5 按列迭代
# 直接对DataFrame迭代
for column in df:
    print(column)

# 依次取出每个列
for column in df:
    print(df[column])

# 可对每个列的内容进行迭代
for column in df:
    for i in df[column]:
        print(i)

# 可以迭代指定列
for i in df.name:
    print(i)
# 只迭代想要的列
l = ['name', 'Q1']
cols = df.columns.intersection(l)
for col in cols:
    print (col)

5.7 函数应用

5.7.1 pipe()
# 对df多重应用多个函数
f(g(h(df), arg1=a), arg2=b, arg3=c)

# 用pipe可以把它们连接起来
(df.pipe(h)
   .pipe(g, arg1=a)
   .pipe(f, arg2=b, arg3=c)
)

# 以下是将'arg2'参数传给函数f,然后作为函数整体接受后面的参数
(df.pipe(h)
   .pipe(g, arg1=a)
   .pipe((f, 'arg2'), arg1=a, arg3=c)
 )

# 定义一个函数,给所有季度的成绩加n,然后增加平均数
# 其中n中要加的值为必传参数
def add_mean(rdf, n):
    df = rdf.copy()
    df = df.loc[:,'Q1':'Q4'].applymap(lambda x: x+n)
    df['avg'] = df.loc[:,'Q1':'Q4'].mean(1)
    return # 调用
df.pipe(add_mean, 100)

# 筛选出Q1大于等于80且Q2大于等于90的数据
df.pipe(lambda df_, x, y: df_[(df_.Q1 >= x) & (df_.Q2 >= y)], 80, 90)

5.7.2 apply()
# 将name全部变为小写
df.name.apply(lambda x: x.lower())

# 去掉一个最高分和一个最低分再算出平均分
def my_mean(s):
    max_min_ser = pd.Series([-s.max(), -s.min()])
    return s.append(max_min_ser).sum()/(s.count()-2)

# 对数字列应用函数
df.select_dtypes(include='number').apply(my_mean)

# 同样的算法以学生为维度计算
(
    df.set_index('name') # 设定name为索引
    .select_dtypes(include='number')
    .apply(my_mean, axis=1) # 横向计算
)

# 判断一个值是否在另一个类似列表的列中
df.apply(lambda d: d.s in d.s_list, axis=1) # 布尔序列
df.apply(lambda d: d.s in d.s_list, axis=1).astype(int) # 0 和 1 序列

# 函数,将大于90分数标记为good
fun = lambda x: np.where(x.team=='A' and x.Q1>90, 'good' ,'other')
df.apply(fun, axis=1)
# 同上效果
(df.apply(lambda x: x.team=='A' and x.Q1>90, axis=1)
 .map({True:'good', False:'other'})
)
df.apply(lambda x: 'good' if x.team=='A' and x.Q1>90 else '', axis=1)

df.apply(fun) # 自定义
df.apply(max) # Python内置函数
df.apply(lambda x: x*2) # lambda
df.apply(np.mean) # NumPy等其他库的函数
df.apply(pd.Series.first_valid_index) # Pandas自己的函数
5.7.3 applymap()
# 计算数据的长度
def mylen(x):
    return len(str(x))

df.applymap(lambda x:mylen(x)) # 应用函数
df.applymap(mylen) # 效果同上

5.7.4 map()
df.team.map({'A':'一班', 'B':'二班','C':'三班', 'D':'四班',}) # 枚举替换
df.team.map('I am a {}'.format)
df.team.map('I am a {}'.format, na_action='ignore')
t = pd.Series({'six': 6., 'seven': 7.})
s.map(t)
# 应用函数
def f(x):
    return len(str(x))

df['name'].map(f)
5.7.5 agg()
# 每列的最大值
df.agg('max')
# 将所有列聚合产生sum和min两行
df.agg(['sum', 'min'])
# 序列多个聚合
df.agg({'Q1' : ['sum', 'min'], 'Q2' : ['min', 'max']})
# 分组后聚合
df.groupby('team').agg('max')
df.Q1.agg(['sum', 'mean'])

def mymean(x):
    return x.mean()

df.Q2.agg(['sum', mymean])

# 每列使用不同的方法进行聚合
df.agg(a=('Q1', max),
       b=('Q2', 'min'),
       c=('Q3', np.mean),
       d=('Q4', lambda s:s.sum()+1)
      )
# 按行聚合
df.loc[:,'Q1':].agg("mean", axis="columns")
# 利用pd.Series.add方法对所有数据加分,other是add方法的参数
df.loc[:,'Q1':].agg(pd.Series.add, other=10)
5.7.6 transform()
df.transform(lambda x: x*2) # 应用匿名函数
df.transform([np.sqrt, np.exp]) # 调用多个函数
df.transform([np.abs, lambda x: x + 1])
df.transform({'A': np.abs, 'B': lambda x: x + 1})
df.transform('abs')
df.transform(lambda x: x.abs())
df.groupby('team').sum()
5.7.7 copy()
s = pd.Series([1, 2], index=["a", "b"])
s_1 = s
s_copy = s.copy()
s_1 is s # True
s_copy is s # Fa

第6章 Pandas分组聚合

6.1 概述

6.1.3 DataFrame应用分组
# 按team分组对应列并相加
df.groupby('team').sum()

# 对不同列使用不同的计算方法
df.groupby('team').agg({'Q1': sum,  # 总和
                        'Q2': 'count', # 总数
                        'Q3':'mean', # 平均
                        'Q4': max}) # 最大值

# 对同一列使用不同的计算方法
df.groupby('team').agg({'Q1': [sum, 'std', max],  # 使用三个方法
                        'Q2': 'count', # 总数
                        'Q3':'mean', # 平均
                        'Q4': max}) # 最大值
6.1.4 Series应用分组
# 对Series df.Q1按team分组,求和
df.Q1.groupby(df.team).sum()

6.2 分组

6.2.1 分组对象
df.groupby('team')
df.Q1.groupby(df.team)
6.2.2 按标签分组
grouped = df.groupby('col') # 单列
grouped = df.groupby('col', axis='columns') # 按行
grouped = df.groupby(['col1', 'col2']) # 多列

# 分组
grouped = df.groupby('team')
# 查看D组
grouped.get_group('D')
6.2.3 表达式
# 索引值是否为偶数,分成两组
df.groupby(lambda x:x%2==0).sum()
df.groupby(df.index%2==0).sum() # 同上

# 按索引是否大于或等于50分为True和False两组
df.groupby(lambda x:x>=50)
df.groupby(df.index>=50).sum() # 同上

# 列名包含Q的分成一组
df.groupby(lambda x:'Q' in x, axis=1).sum()

# 按索引奇偶行分为True和False两组
df.groupby(df.index%2==0) # 同上例
# 按姓名首字母分组
df.groupby(df.name.str[0])
# 按A及B、其他团队分组
df.groupby(df.team.isin(['A','B']))
# 按姓名第一个字母和第二个字母分组
df.groupby([df.name.str[0], df.name.str[1]])
# 按日期和小时分组
df.groupby([df.time.date, df.time.hour])
6.2.4 函数分组
# 从时间列time中提取年份来分组
df.groupby(df.time.apply(lambda x:x.year)).count()

# 按姓名首字母为元音、辅音分组
def get_letter_type(letter):
    if letter[0].lower() in 'aeiou':
        return '元音'
    else:
        return '辅音'

df.set_index('name').groupby(get_letter_type).sum()

6.2.5 多种方法混合
# 按team、姓名首字母是否为元音分组
df.groupby(['team', df.name.apply(get_letter_type)]).sum()

6.2.6 用pipe调用分组方法
# 使用pipe调用分组函数
df.pipe(pd.DataFrame.groupby, 'team').sum()
6.2.7 分组器Grouper
# 分组器语法
pandas.Grouper(key=None, level=None, freq=None, axis=0, sort=False)
df.groupby(pd.Grouper('team'))

df.groupby(pd.Grouper('team')).sum()
# 如果是时间,可以60秒一分组
df.groupby(Grouper(key='date', freq='60s'))

# 轴方向
df.groupby(Grouper(level='date', freq='60s', axis=1))
# 按索引
df.groupby(pd.Grouper(level=1)).sum()
# 多列
df.groupby([pd.Grouper(freq='1M', key='Date'), 'Buyer']).sum()
df.groupby([pd.Grouper('dt', freq='D'),
            pd.Grouper('other_column')
           ])

# 按轴层级
df.groupby([pd.Grouper(level='second'), 'A']).sum()
df.groupby([pd.Grouper(level=1), 'A']).sum()

# 按时间周期分组
df['column_name'] = pd.to_datetime(df['column_name'])
df.groupby(pd.Grouper(key='column_name', freq="M")).mean()

# 10年一个周期
df.groupby(pd.cut(df.date,
                  pd.date_range('1970', '2020', freq='10YS'),
                  right=False)
          ).mean()
6.2.8 索引
df.groupby('team', as_index=False).sum()
6.2.9 排序
# 不对索引进行排序
df.groupby('team', sort=False).sum()

6.3 分组对象的操作

# 分组,为了方便案例介绍,删去name列,分组后全为数字
grouped = df.drop('name', axis=1).groupby('team')
grouped.sum()
6.3.1 选择分组
# 查看分组内容
df.groupby('team').groups

# 用团队和姓名首字母分组
grouped2 = df.groupby(['team', df.name.str[0]])
# 选择B组、姓名以A开头的数据
grouped2.get_group(('B', 'A'))

# 获取分组字典数据
grouped.indices

# 选择A组
grouped.indices['A']
6.3.2 迭代分组
# 迭代
for g in grouped:
    print(type(g))

# 迭代元素的数据类型
for name, group in grouped:
    print(type(name))
    print(type(group))
6.3.3 选择列
# 选择分组后的某一列
grouped.Q1
grouped['Q1'] 
# 选择多列
grouped[['Q1','Q2']]
# 对多列进行聚合计算
grouped[['Q1','Q2']].sum()
6.3.4 应用函数apply()
# 将所有元素乘以2
df.groupby('team').apply(lambda x: x*2)

# 按分组将一列输出为列表
df.groupby('team').apply(lambda x: x['name'].to_list())

# 各组Q1(为参数)成绩最高的前三个
def first_3(df_, c):
    return df_[c].sort_values(ascending=False).head(3)

# 调用函数
df.set_index('name').groupby('team').apply(first_3, 'Q1')

(
    df.groupby('team')
    .apply(lambda x: pd.Series({
        'Q1_sum'       : x['Q1'].sum(),
        'Q1_max'       : x['Q1'].max(),
        'Q2_mean'      : x['Q2'].mean(),
        'Q4_prodsum'   : (x['Q4'] * x['Q4']).sum()
    }))
)

# 定义一个函数
def f_mi(x):
        d = []
        d.append(x['Q1'].sum())
        d.append(x['Q2'].max())
        d.append(x['Q3'].mean())
        d.append((x['Q4'] * x['Q4']).sum())
        return pd.Series(d, index=[['Q1', 'Q2', 'Q3', 'Q4'],
                                   ['sum', 'max', 'mean', 'prodsum']])
# 使用函数
df.groupby('team').apply(f_mi)
6.3.5 管道方法pipe()
# 每组最大值和最小值之和
df.groupby('team').pipe(lambda x: x.max() + x.min())

# 定义了A组和B组平均值的差值
def mean_diff(x):
    return x.get_group('A').mean() - x.get_group('B').mean()

# 使用函数
df.groupby('team').pipe(mean_diff)
6.3.6 转换方法transform()
# 将所有数据替换成分组中的平均成绩
df.groupby('team').transform(np.mean)

df.groupby('team').transform(max) # 最大值
df.groupby('team').transform(np.std) # 标准差
# 使用函数,和上一个学生的差值(没有处理姓名列)
df.groupby('team').transform(lambda x: x.shift(-1))

def score(gb):
    return (gb - gb.mean()) / gb.std()*10
grouped.transform(score)

# Q1成绩大于60的组的所有成员
df[df.groupby('team').transform('mean').Q1 > 60]
6.3.7 筛选方法filter()
# 每组每个季度的平均分
df.groupby('team').mean()
# Q1成绩至少有一个大于97的组
df.groupby(['team']).filter(lambda x: (x['Q1'] > 97).any())
# 所有成员平均成绩大于60的组
df.groupby(['team']).filter(lambda x: (x.mean() >= 60).all())
# Q1所有成员成绩之和超过1060的组
df.groupby('team').filter(lambda g: g.Q1.sum() > 1060)
6.3.8 其他功能
df.groupby('team').first() # 组内第一个
df.groupby('team').last() # 组内最后一个
df.groupby('team').ngroups # 5(分组数)
df.groupby('team').ngroup() # 分组序号

grouped.backfill()
grouped.bfill()
df.groupby('team').head() # 每组显示前5个
grouped.tail(1) # 每组最后一个
grouped.rank() # 排序值
grouped.fillna(0)
grouped.indices() # 组名:索引序列组成的字典

# 分组中的第几个值
gp.nth(1) # 第一个
gp.nth(-1) # 最后一个
gp.nth([-2, -1])
# 第n个非空项
gp.nth(0, dropna='all')
gp.nth(0, dropna='any')

df.groupby('team').shift(-1) # 组内移动
grouped.tshift(1) # 按时间周期移动

df.groupby('team').any()
df.groupby('team').all()

df.groupby('team').rank() # 在组内的排名

# 仅 SeriesGroupBy 可用
df.groupby("team").Q1.nlargest(2) # 每组最大的两个
df.groupby("team").Q1.nsmallest(2) # 每组最小的两个
df.groupby("team").Q1.nunique() # 每组去重数量
df.groupby("team").Q1.unique() #  每组去重值
df.groupby("team").Q1.value_counts() #  每组去重值及数量
df.groupby("team").Q1.is_monotonic_increasing # 每组值是否单调递增
df.groupby("team").Q1.is_monotonic_decreasing # 每组值是否单调递减

# 仅 DataFrameGroupBy 可用
df.groupby("team").corrwith(df2) # 相关性

6.4 聚合统计

6.4.1 描述统计
# 描述统计
df.groupby('team').describe()
# 由于列过多,我们进行转置
df.groupby('team').describe().T
6.4.2 统计函数
# 各组平均数
grouped.mean()
df.groupby('team').describe() # 描述性统计
df.groupby('team').sum() # 求和
df.groupby('team').count() # 每组数量,不包括缺失值
df.groupby('team').max() # 求最大值
df.groupby('team').min() # 求最小值
df.groupby('team').size() # 分组数量
df.groupby('team').mean() # 平均值
df.groupby('team').median() # 中位数
df.groupby('team').std() # 标准差
df.groupby('team').var() # 方差
grouped.corr() # 相关性系数
grouped.sem() # 标准误差
grouped.prod() # 乘积
grouped.cummax() # 每组的累计最大值
grouped.cumsum() # 累加
grouped.mad() # 平均绝对偏差
6.4.3 聚合方法agg()
# 所有列使用一个计算方法
df.groupby('team').aggregate(sum)
df.groupby('team').agg(sum)
grouped.agg(np.size)
grouped['Q1'].agg(np.mean)

# 每个字段使用多个计算方法
grouped[['Q1','Q3']].agg([np.sum, np.mean, np.std])

df.groupby('team').agg({'Q1': ['min', 'max'], 'Q2': 'sum'})

# 指定列名,列表是为原列和方法
df.groupby('team').Q1.agg(Mean='mean', Sum='sum')
df.groupby('team').agg(Mean=('Q1', 'mean'), Sum=('Q2', 'sum'))
df.groupby('team').agg(
    Q1_max=pd.NamedAgg(column='Q1', aggfunc='max'),
    Q2_min=pd.NamedAgg(column='Q2', aggfunc='min')
)

df.groupby('team').agg(**{
    '1_max':pd.NamedAgg(column='Q1', aggfunc='max')})

# 聚合结果使用函数
def max_min(x):
    return x.max() - x.min()
# 定义函数
df.groupby('team').Q1.agg(Mean='mean',
                          Sum='sum',
                          Diff=lambda x: x.max() - x.min(),
                          Max_min=max_min
                         )

df.groupby('team').agg(max_min)
6.4.4 时序重采样方法resample()
idx = pd.date_range('1/1/2020', periods=100, freq='T')
df2 = pd.DataFrame(data={'a':[0, 1]*50, 'b':1},
                   index=idx)

# 每20分钟聚合一次
df2.groupby('a').resample('20T').sum()

df.groupby('a').resample('3T').sum()
# 30秒一分组
df.groupby('a').resample('30S').sum()
# 每月
df.groupby('a').resample('M').sum()
# 以右边时间点为标识
df.groupby('a').resample('3T', closed='right').sum()
6.4.5 组内头尾值
df.groupby('team').first()
6.4.6 组内分位数
# 二分位数,即中位数
df.groupby('team').median() 
df.groupby('team').quantile()
df.groupby('team').quantile(0.5)
6.4.7 组内差值
# grouped为全数字列,计算在组内的前后差值
grouped.diff()

6.5 数据分箱

6.5.1 定界分箱pd.cut()
# 将Q1成绩换60分及以上、60分以下进行分类
pd.cut(df.Q1, bins=[0, 60, 100])

# Series使用
df.Q1.groupby(pd.cut(df.Q1, bins=[0, 60, 100])).count()

# 不显示区间,使用数字作为每个箱子的标签,形式如0,1,2,n等
pd.cut(df.Q1, bins=[0, 60, 100],labels=False)
# 指定标签名
pd.cut(df.Q1, bins=[0, 60, 100],labels=['不及格','及格',])
# 包含最低部分
pd.cut(df.Q1, bins=[0, 60, 100], include_lowest=True)
# 是否为右闭区间,下例为[89, 100)
pd.cut(df.Q1, bins=[0, 89, 100], right=False)
6.5.2 等宽分箱pd.qcut()
# 按Q1成绩分为两组
pd.qcut(df.Q1,q=2)

# Series使用
df.Q1.groupby(pd.qcut(df.Q1,q=2)).count()

pd.qcut(range(5), 4)
pd.qcut(range(5), 4, labels=False)
# 指定标签名
pd.qcut(range(5), 3, labels=["good", "medium", "bad"])
# 返回箱子标签 array([1. , 51.5, 98.]))
pd.qcut(df.Q1, q=2, retbins=True)
# 分箱位小数位数
pd.qcut(df.Q1, q=2, precision=3)
# 排名分3个层次
pd.qcut(df.Q1.rank(method='first'), 3)

6.6 分组可视化

6.6.1 绘图方法plot()
# 分组,设置索引为name
grouped = df.set_index('name').groupby('team')
# 绘制图形
grouped.plot()
6.6.2 直方图hist()
# 绘制直方图
grouped.hist()

# 共生成5组直方图
6.6.3 箱线图boxplot()
# 分组箱线图
grouped.boxplot(figsize=(15,12))

# 分组箱线图
df.boxplot(by="team", figsize=(15,10))

第7章 Pandas数据合并与对比

7.1 数据追加df.append

7.1.1 基本语法
# 语法结构
df.append(self, other, ignore_index=False,
          verify_integrity=False, sort=False)
7.1.2 相同结构
df1 = pd.DataFrame({'x': [1,2], 'y': [3, 4]})

# 追加多个数据
df1.append([df2, df2, df2])
7.1.3 不同结构
df3 = pd.DataFrame({'y': [5,6], 'z': [7, 8]})
7.1.4 忽略索引
# 忽略索引
df1.append(df2, ignore_index=True)
7.1.5 重复内容
df1.append([df2, df2], verify_integrity=True)
7.1.6 追加序列
df.tail()
7.1.7 追加字典
lily = {'name': 'lily', 'team': 'C', 'Q1':55, 'Q2':56, 'Q3':57, 'Q4':58}
df = df.append(lily, ignore_index=True)

7.2 数据连接pd.concat

7.2.2 简单连接
pd.concat([df1, df2])

# 效果同上
df1.append(df2)
7.2.3 按列连接
df2 = pd.DataFrame({'x': [5, 6, 0], 'y': [7, 8, 0]})
7.2.4 合并交集
# 按列合并交集
pd.concat([df1, df2], axis=1, join='inner')

# 两种方法
pd.concat([df1, df2], axis=1).reindex(df1.index)
pd.concat([df1, df2.reindex(df1.index)], axis=1)
7.2.5 与序列合并
z = pd.Series([9, 9], name='z')
# 将序列加到新列
pd.concat([df1, z], axis=1)
# 增加新列
df1.assign(z=z)
7.2.6 指定索引
# 指定索引名
pd.concat([df1, df2], keys=['a', 'b'])

7.2.7 多文件合并
# 通过各种方式读取数据
df1 = pd.DataFrame(data1)
df2 = pd.read_excel('tmp.xlsx')
df3 = pd.read_csv('tmp.csv')

# 合并数据
merged_df = pd.concat([df1, df2, df3])

# process_your_file(f)方法将文件读取为DataFrame
frames = [process_your_file(f) for f in files]
# 合并
result = pd.concat(frames)
7.2.8 目录文件合并
import glob
# 取出目录下所有XLSX格式的文件
files = glob.glob("data/*.xlsx")
cols = ['ID', '时间', '名称'] # 只取这些列
# 列表推导出对象
dflist = [pd.read_excel(i, usecols=cols) for i in files]
df = pd.concat(dflist) # 合并

# 使用pd.read_csv逐一读取文件,然后合并
pd.concat(map(pd.read_csv, ['data/d1.csv',
                            'data/d2.csv',
                            'data/d3.csv']))

# 使用pd.read_excel逐一读取文件,然后合并
pd.concat(map(pd.read_excel, ['data/d1.xlsx',
                              'data/d2.xlsx',
                              'data/d3.xlsx']))

# 目录下的所有文件
from os import listdir
filepaths = [f for f in listdir("./data") if f.endswith('.csv')]
df = pd.concat(map(pd.read_csv, filepaths))

# 其他方法
import glob
df = pd.concat(map(pd.read_csv, glob.glob('data/*.csv')))
df = pd.concat(map(pd.read_excel, glob.glob('data/*.xlsx')))

7.3 数据合并pd.merge

7.3.2 连接键
df1 = pd.DataFrame({'a': [1, 2], 'x': [5, 6]})
7.3.3 索引连接
pd.merge(df1, df2,
         left_index=True,
         right_index=True,
         suffixes=('_1', '_2')
        )

7.3.4 多连接键
df3 = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'x': [5, 6]})
7.3.5 连接方法
# 以左表为基表
pd.merge(df3, df4, how='left', on=['a', 'b'])

# 取两个表的并集
pd.merge(left, right, how='outer', on=['key1', 'key2'])

# 取两个表的交集
pd.merge(left, right, how='inner', on=['key1', 'key2'])

# 一个有重复连接键的例子
left = pd.DataFrame({'A': [1, 2], 'B': [2, 2]})
right = pd.DataFrame({'A': [4, 5, 6], 'B': [2, 2, 2]})
pd.merge(left, right, on='B', how='outer')
7.3.6 连接指示
# 显示连接指示列
pd.merge(df1, df2, on='a', how='outer', indicator=True)

7.4 按元素合并

7.4.1 df.combine_first()
df1 = pd.DataFrame({'A': [None, 1], 'B': [None, 2]})
df1 = pd.DataFrame({'A': [None, 1], 'B': [2, None]})
7.4.2 df.combine()
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})

# 取最大值,即上例的实现
df1.combine(df2, np.maximum)
# 取对应最小值
df1.combine(df2, np.minimum)

7.5 数据对比df.compare

7.5.1 简单对比
df1 = pd.DataFrame({'a': [1, 2], 'b': [5, 6]})
df2 = pd.DataFrame({'a': [0, 2], 'b': [5, 7]})
# 对比数据
df1.compare(df2)
7.5.2 对齐方式
# 对比数据
df1.compare(df2, align_axis=0)
7.5.3 显示相同值
# 对比数据
df1.compare(df2, keep_equal=True)
7.5.4 保持形状
# 对比数据
df1.compare(df2, keep_shape=True)
# 对比数据
df1.compare(df2, keep_shape=True, keep_equal=True)

第8章 Pandas多层索引

8.1 概述

8.1.2 通过分组产生多层索引
# 按团队分组,各团队中平均成绩及格的人数
df.groupby(['team', df.mean(1)>=60]).count()

8.1.3 由序列创建多层索引
# 定义一个序列
arrays = [[1, 1, 2, 2], ['A', 'B', 'A', 'B']]
# 生成多层索引
index = pd.MultiIndex.from_arrays(arrays, names=('class', 'team'))
index

# 指定的索引是多层索引
pd.DataFrame([{'Q1':60, 'Q2':70}], index=index)

8.1.4 由元组创建多层索引
# 定义一个两层的序列
arrays = [[1, 1, 2, 2], ['A', 'B', 'A', 'B']]
# 转换为元组
tuples = list(zip(*arrays))
tuples
# [(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')]
# 将元组转换为多层索引对象
index = pd.MultiIndex.from_tuples(tuples, names=['class', 'team'])
# 使用多层索引对象
pd.Series(np.random.randn(4), index=index)

8.1.5 可迭代对象的笛卡儿积
_class = [1, 2]
team = ['A', 'B']
# 生成多层索引对象
index = pd.MultiIndex.from_product([_class, team],
                                   names=['class', 'team'])
# Series应用多层索引对象
pd.Series(np.random.randn(4), index=index)

8.1.6 将DataFrame转为多层索引对象
df_i = pd.DataFrame([['1', 'A'], ['1', 'B'],
                     ['2', 'B'], ['2', 'B']],
                    columns=['class', 'team'])

8.2 多层索引操作

8.2.1 生成数据
# 索引
index_arrays = [[1, 1, 2, 2], ['男', '女', '男', '女']]
# 列名
columns_arrays = [['2019', '2019', '2020', '2020'],
                  ['上半年', '下半年', '上半年', '下半年',]]
# 索引转换为多层
index = pd.MultiIndex.from_arrays(index_arrays,
                                  names=('班级', '性别'))
# 列名转换为多层
columns = pd.MultiIndex.from_arrays(columns_arrays,
                                    names=('年份', '学期'))
# 应用到DataFrame中
df = pd.DataFrame([(88,99,88,99),(77,88,97,98),
                   (67,89,54,78),(34,67,89,54)],
                  columns=columns, index=index)
8.2.2 索引信息
df.index # 索引,是一个MultiIndex
# 查看行索引的名称
df.index.names
# 查看列索引的名称
df.columns.names
8.2.3 查看层级
df.index.nlevels # 行层级数
df.index.levels # 行的层级
df.columns.nlevels  # 列层级数
df.columns.levels # 列的层级
df[['2019','2020']].index.levels # 筛选后的层级
8.2.4 索引内容
# 获取索引第2层内容
df.index.get_level_values(1)
# 获取列索引第1层内容
df.columns.get_level_values(0)
# 按索引名称取索引内容
df.index.get_level_values('班级')
df.columns.get_level_values('年份')
8.2.5 排序
# 使用索引名可进行排序,可以指定具体的列
df.sort_values(by=['性别', ('2020','下半年')])
df.index.reorder_levels([1,0]) # 等级顺序,互换
idx.set_codes([1, 1, 0, 0], level='foo') # 设置顺序
df.index.sortlevel(level=0, ascending=True) # 按指定级别排序
df.index.reindex(df.index[::-1]) # 更换顺序,或者指定一个顺序
8.2.6 其他操作
df.index.to_numpy() # 生成一个笛卡儿积的元组对序列
df.index.remove_unused_levels() # 返回没有使用的层级
df.swaplevel(0, 2) # 交换索引
df.to_frame() # 转为DataFrame
idx.set_levels(['a', 'b'], level='bar') # 设置新的索引内容
idx.set_levels([['a', 'b', 'c'], [1, 2, 3, 4]], level=[0, 1])
idx.to_flat_index() # 转为元组对序列
df.index.droplevel(0) # 删除指定等级
df.index.get_locs((2, '女'))  # 返回索引的位置

8.3 数据查询

8.3.1 查询行
df.loc[(1, '男')]
8.3.4 条件查询
df[df[('2020','上半年')] > 80]
8.3.5 用pd.IndexSlice索引数据
idx = pd.IndexSlice
idx[0]               # 0
idx[:]               # slice(None, None, None)
idx[0,'x']           # (0, 'x')
idx[0:3]             # slice(0, 3, None)
idx[0.1:1.5]         # slice(0.1, 1.5, None)
idx[0:5,'x':'y']     # (slice(0, 5, None), slice('x', 'y', None))

idx = pd.IndexSlice
df.loc[idx[:,['男']],:] # 只显示男生
df.loc[:,idx[:,['上半年']]] # 只显示上半年
8.3.6 df.xs()
df.xs((1, '男')) # 1班男生
df.xs('2020', axis=1) # 2020年
df.xs('男', level=1) # 所有男生

第9章 Pandas数据重塑与透视

9.1 数据透视

9.1.2 整理透视操作
# 透视,指定索引、列、值
df.pivot(index='A', columns='B', values='C')

# 不指定值内容
df.pivot(index='A', columns='B')

# 指定多列值
df.pivot(index='A', columns='B', values=['C', 'D'])

9.1.3 聚合透视
9.1.4 聚合透视操作

# 透视
pd.pivot_table(df, index='A', columns='B', values='D')
# 筛选a2和b1的数据
df.loc[(df.A=='a2') & (df.B=='b1')]
# 对D求平均数
df.loc[(df.A=='a2') & (df.B=='b1')].D.mean()
9.1.5 聚合透视高级操作
# 高级聚合
pd.pivot_table(df, index=['A', 'B'], # 指定多个索引
               columns=['C'], # 指定列
               values='D', # 指定数据值
               aggfunc=np.sum, # 指定聚合方法为求和
               fill_value=0, # 将聚合为空的值填充为0
               margins=True # 增加行列汇总
              )

# 使用多个聚合计算
pd.pivot_table(df, index=['A', 'B'], # 指定多个索引
               columns=['C'], # 指定列
               values='D', # 指定数据值
               aggfunc=[np.mean, np.sum]
              )

9.2 数据堆叠

9.2.2 堆叠操作df.stack()
# 设置多层索引
df.set_index(['A', 'B'], inplace=True)

# 堆叠
df.stack()

# 查看类型
type(df.stack())

9.3 交叉表

9.3.2 生成交叉表
# 对分类数据做交叉
one = pd.Categorical(['a', 'b'], categories=['a', 'b', 'c'])
two = pd.Categorical(['d', 'e'], categories=['d', 'e', 'f'])
pd.crosstab(one, two)

9.3.3 归一化
pd.crosstab(df['A'], df['B'])

9.4 数据转置df.T

9.4.3 类型变化
df.dtypes
9.4.4 轴交换df.swapaxes()
df.swapaxes("index", "columns") # 行列交换,相当于df.T
df.swapaxes("columns", "index") # 同上
df.swapaxes("index", "columns", copy=True) # 使生效
df.swapaxes("columns", "columns") # 无变化
df.swapaxes("index", "index") # 无变化

9.5 数据融合

9.5.1 基本语法
pd.melt(frame: pandas.core.frame.DataFrame,
        id_vars=None, value_vars=None,
        var_name='variable', value_name='value',
        col_level=None)

9.6 虚拟变量

9.6.2 生成虚拟变量
# 原数据
df = pd.DataFrame({'a': list('adcb'),
                   'b': list('fehg'),
                   'a1': range(4),
                   'b1': range(4,8)})
9.6.4 从DataFrame生成
# 只生成b列的虚拟变量
pd.get_dummies(df, columns=['b'])

9.7 因子化

9.7.1 基本方法
# 数据
data = ['b', 'b', 'a', 'c', 'b']

# 因子化
codes, uniques = pd.factorize(data)
cat = pd.Series(['a', 'a', 'c'])
codes, uniques = pd.factorize(cat)
9.7.2 排序
codes, uniques = pd.factorize(['b', 'b', 'a', 'c', 'b'], sort=True)
9.7.3 缺失值
codes, uniques = pd.factorize(['b', None, 'a', 'c', 'b'])
9.7.4 枚举类型
cat = pd.Categorical(['a', 'a', 'c'], categories=['a', 'b', 'c'])
codes, uniques = pd.factorize(cat)

9.8 爆炸列表

9.8.3 非列表格式
# 原数据
df = pd.DataFrame([{'var1': 'a,b,c', 'var2': 1},
                   {'var1': 'd,e,f', 'var2': 2}])

# 使用指定同名列的方式对列进行修改
df.assign(var1=df.var1.str.split(',')).explode('var1')

第10章 Pandas数据清洗

10.1.1 缺失值类型
# 将无穷值设置为缺失值
pd.options.mode.use_inf_as_na = True
10.1.2 缺失值判断
# 检测缺失值
df.isna()
# 检测缺失值
df.D.isna()
# 检测非缺失值
df.notna()
10.1.3 缺失值统计
# 布尔值的求和
pd.Series([True, True, False]).sum()
# 每列有多少个缺失值
df.isnull().sum()
# 总共有多少个缺失值
df.isna().sum().sum()
10.1.4 缺失值筛选
# 有缺失值的列
df.loc[:,df.isna().any()]
# 没有缺失值的行
df.loc[~(df.isna().any(1))]
10.1.5 NA标量
s = pd.Series([1, 2, None, 4], dtype="Int64")
s[2]
# <NA>
s[2] is pd.NA
# True
pd.isna(pd.NA)
# True
# 加法
pd.NA + 1
# <NA>
# 乘法
'a' * pd.NA
# <NA>
pd.NA ** 0
# 1
1 ** pd.NA
# 1
pd.NA == 1
# <NA>
pd.NA == pd.NA
# <NA>
pd.NA < 2.5
# <NA>
10.1.6 时间数据中的缺失值
# 时间数据中的缺失值
pd.Series([pd.Timestamp('20200101'), None, pd.Timestamp('20200103')])
10.1.7 整型数据中的缺失值
type(df.at[2,'D'])
# numpy.float64
pd.Series([1, 2, np.nan, 4], dtype=pd.Int64Dtype())
10.1.8 插入缺失值
# 修改为缺失值
df.loc[0] = None
df.loc[1] = np.nan
df.A = pd.NA

10.2 缺失值的操作

10.2.1 缺失值填充
# 填充为 0
df.fillna(0)
# 填充为指定字符
df.fillna('missing')
df.fillna('暂无')
df.fillna('待补充')
# 指定字段填充
df.one.fillna('暂无')
# 指定字段填充
df.one.fillna(0, inplace=True)
# 只替换第一个
df.fillna(0, limit=1)
# 将不同列的缺失值替换为不同的值
values = {'A': 0, 'B': 1, 'C': 2, 'D': 3}
df.fillna(value=values)

# 填充列的平均值
df.fillna(dff.mean())
# 对指定列填充平均值
df.fillna(dff.mean()['B':'C'])
# 另一种填充列的平均值的方法
df.where(pd.notna(df), dff.mean(), axis='columns')

# 将指定列的空值替换成指定值
df.replace({'toy': {np.nan: 100}})
10.2.2 插值填充
s = pd.Series([0, 1, np.nan, 3])
# 插值填充
s.interpolate()
10.2.3 缺失值删除
# 删除所有有缺失值的行
df.dropna()
# 删除所有有缺失值的列
df.dropna(axis='columns')
df.dropna(axis=1)
# 删除所有值都缺失的行
df.dropna(how='all')
# 删除至少有两个缺失值的行
df.dropna(thresh=2)
# 指定判断缺失值的列范围
df.dropna(subset=['name', 'born'])
# 使删除的结果生效
df.dropna(inplace=True)
# 指定列的缺失值删除
df.col.dropna()
10.2.4 缺失值参与计算
# 累加
df.D.cumsum()
# 累加,跳过空值
df.D.cumsum(skipna=False)
# 缺失值不计数
df.count()
# 聚合计入缺失值
df.groupby('B', dropna=False).sum()

10.3 数据替换

10.3.1 指定值替换
ser = pd.Series([0., 1., 2., 3., 4.])
ser.replace(0, 5)

# 一一对应进行替换
ser.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0])
# 用字典映射对应替换值
ser.replace({0: 10, 1: 100})
# 将a列的0、b列中的5替换为100
df.replace({'a': 0, 'b': 5}, 100)
#  指定列里的替换规则
df.replace({'a': {0: 100, 4: 400}})
10.3.2 使用替换方式
# 将 1,2,3 替换为它们前一个值
ser.replace([1, 2, 3], method='pad') # ffill是它同义词
# 将 1,2,3 替换为它们后一个值
ser.replace([1, 2, 3], method='bfill')
10.3.3 字符替换
# 把bat替换为new,不使用正则表达式
df.replace(to_replace='bat', value='new')
# 利用正则表达式将ba开头的值替换为new
df.replace(to_replace=r'^ba.$', value='new', regex=True)
# 如果多列规则不一,可以按以下格式对应传入
df.replace({'A': r'^ba.$'}, {'A': 'new'}, regex=True)
# 多个规则均替换为同样的值
df.replace(regex=[r'^ba.$', 'foo'], value='new')
# 多个正则及对应的替换内容
df.replace(regex={r'^ba.$': 'new', 'foo': 'xyz'})
10.3.4 缺失值替换
d = {'a': list(range(4)),
     'b': list('ab..'),
     'c': ['a', 'b', np.nan, 'd']
    }
df = pd.DataFrame(d)
# 将.替换为NaN
df.replace('.', np.nan)
# 使用正则表达式,将空格等替换为NaN
df.replace(r'\s*\.\s*', np.nan, regex=True)
# 对应替换,a换b,点换NaN
df.replace(['a', '.'], ['b', np.nan])
# 点换dot,a换astuff
df.replace([r'\.', r'(a)'], ['dot', r'\1stuff'], regex=True)
# b中的点要替换,将b替换为NaN,可以多列
df.replace({'b': '.'}, {'b': np.nan})
# 使用正则表达式
df.replace({'b': r'\s*\.\s*'}, {'b': np.nan}, regex=True)
# b列的b值换为空
df.replace({'b': {'b': r''}}, regex=True)
# b列的点、空格等替换为NaN
df.replace(regex={'b': {r'\s*\.\s*': np.nan}})
# 在b列的点后加ty,即.ty
df.replace({'b': r'\s*(\.)\s*'},
           {'b': r'\1ty'},
           regex=True)
# 多个正则规则
df.replace([r'\s*\.\s*', r'a|b'], np.nan, regex=True)
# 用参数名传参
df.replace(regex=[r'\s*\.\s*', r'a|b'], value=np.nan)

s = pd.Series([10, 'a', 'a', 'b', 'a'])
# 将a换为None
s.replace({'a': None})
# 会使用前一个值,前两个为10,最后一个为b
s.replace('a', None)
10.3.5 数字替换
# 生成数据
df = pd.DataFrame(np.random.randn(10, 2))
df[np.random.rand(df.shape[0]) > 0.5] = 1.5

# 将1.5换为NaN,同时将左上角的值换为a
df.replace([1.5, df.iloc[0, 0]], [np.nan, 'a'])
# 使替换生效
df.replace(1.5, np.nan, inplace=True)

10.4 重复值及删除数据

10.4.1 重复值识别
# 检测重复值语法
df.duplicated(subset=None, keep='first')
# 筛选出重复内容
df[df.duplicated()]

10.4.2 删除重复值
# 删除重复值语法
df.drop_duplicates(subset=None,
                   keep='first',
                   inplace=False,
                   ignore_index=False)

10.5 NumPy格式转换

10.5.2 DataFrame转为ndarray
df.values # 不推荐
df.to_numpy()

type(df.to_numpy())
# numpy.ndarray
df.to_numpy().dtype
# dtype('O')
type(df.to_numpy().dtype)
# numpy.dtype

# 转换指定的列
df[['name', 'Q1']].to_numpy()
10.5.3 Series转为ndarray
df.Q1.values # 不推荐
df.Q1.to_numpy()

type(df.Q1.to_numpy())
# numpy.ndarray
df.Q1.to_numpy().dtype
# dtype('int64')
type(df.Q1.to_numpy().dtype)
# numpy.dtype
type(df.Q1.to_numpy())
df.Q1.array
type(df.Q1.array)
10.5.4 df.to_records()
# 转为NumPy record array
df.to_records()
type(df.to_records()) # numpy.recarray
np.array(df.to_records()) # 转为array
10.5.5 np.array读取
np.array(df) # Dataframe转
np.array(df.Q1) # 直接转
np.array(df.Q1.array) # PandasArray转
np.array(df.to_records().view(type=np.matrix)) # 转为矩阵

第11章 Pandas文本处理

11.1 数据类型

11.1.1 文本数据类型
# 指定数据类型
pd.Series(['a', 'b', 'c'], dtype="string")
pd.Series(['a', 'b', 'c'], dtype=pd.StringDtype())
11.1.2 类型转换
# 类型转换,支持string类型
df.convert_dtypes().dtypes
s = pd.Series(['a', 'b', 'c'])
s.astype("object") # 转换为object
s.astype("string") # 转换为string
11.1.3 类型异同
# 数值为Int64
pd.Series(["a", None, "b"]).str.count("a") # dtype: float64
pd.Series(["a", None, "b"], dtype="string").str.count("a")

# 逻辑判断为boolean
pd.Series(["a", None, "b"]).str.isdigit() # dtype: object
pd.Series(["a", None, "b"], dtype="string").str.isdigit()

11.2 字符的操作

11.2.1 .str访问器
# 原数据
s = pd.Series(['A', 'Boy', 'C', np.nan], dtype="string")
# 转为小写
s.str.lower()
# 转为object
df.Q1.astype(str).str
# 转为StringDtype
df.team.astype("string").str
df.Q1.astype(str).astype("string").str
# 对索引进行操作
df.index.str.lower()
# 对表头、列名进行操作
df.columns.str.lower()
# 移除字符串头尾空格&小写&替换下划线
df.columns.str.strip().str.lower().str.replace(' ', '_')
11.2.2 文本格式
s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
s.str.lower() # 转为小写
s.str.upper() # 转为大写
s.str.title() # 标题格式,每个单词大写
s.str.capitalize() # 首字母大写
s.str.swapcase() # 大小写互换
s.str.casefold() # 转为小写,支持其他语言(如德语)
11.2.3 文本对齐
# 居中对齐,宽度为10,用'-'填充
s.str.center(10, fillchar='-')
# 左对齐
s.str.ljust(10, fillchar='-')
# 右对齐
s.str.rjust(10, fillchar='-')
# 指定宽度,填充内容对齐方式,填充内容
# 参数side可取值为left、right或both}, 默认值为left
s.str.pad(width=10, side='left', fillchar='-')
# 填充对齐
s.str.zfill(3) # 生成字符,不足3位的在前面加0
11.2.4 计数和编码
# 字符串中指定字母的数量
s.str.count('a')
# 字符串长度
s.str.len()
s.str.encode('utf-8')
s.str.decode('utf-8')
# 字符串的Unicode普通格式
s.str.normalize('NFC')
11.2.5 格式判定
s.str.isalpha # 是否为字母
s.str.isnumeric # 是否为数字0~9
s.str.isalnum # 是否由字母或数字组成
s.str.isdigit # 是否为数字
s.str.isdecimal # 是否为小数
s.str.isspace # 是否为空格
s.str.islower # 是否小写
s.str.isupper # 是否大写
s.str.istitle # 是否标题格式

11.3 文本高级处理

11.3.1 文本分隔
# 构造数据
s = pd.Series(['天_地_人', '你_我_他', np.nan, '风_水_火'], dtype="string")

# 取出每行第二个
s.str.split('_').str[1]
# get只能传一个值
s.str.split('_').str.get(1)

# []可以使用切片操作
s.str.split('_').str[1:3]
s.str.split('_').str[:-2]
# 如果不指定分隔符,会按空格进行分隔
s.str.split()
# 限制分隔的次数,从左开始,剩余的不分隔
s.str.split(n=2)
11.3.2 字符分隔展开
# 分隔后展开为DataFrame
s.str.split('_', expand=True)

# 从右分隔为两部分后展开为DataFrame
s.str.rsplit('_', expand=True, n=1)

# 数据
s = pd.Series(["你和我及他"])
# 用正则表达式代表分隔位
s.str.split(r"\和|及", expand=True)
11.3.3 文本切片选择
s = pd.Series(["sun", "moon", "star"])

s.str.slice() # 不做任何事
# 切除最后一个以前的,留下最后一个
s.str.slice(start=-1) # s.str[-1]
# 切除第二位以后的
s.str.slice(stop=2) # s.str[:2]
# 切除步长为2的内容
s.str.slice(step=2) # s.str[::2]
# 切除从头开始,第4位以后并且步长为3的内容
# 同s.str[0:5:3]
s.str.slice(start=0, stop=5, step=3)
11.3.4 文本划分
# 构造数据
s = pd.Series(['How are you', 'What are you doing'])
# 从右开始划分
s.str.rpartition()

11.3.5 文本替换
# 带有货币符的数据
s = pd.Series(['10', '-¥20', '¥3,000'], dtype="string")
# 将人民币符号替换为空
s.str.replace('¥', '')
11.3.6 指定替换
# 构造数据
s = pd.Series(['ax', 'bxy', 'cxyz'])
# 保留第一个字符,其他的替换或者追加T
s.str.slice_replace(1, repl='T')
11.3.7 重复替换
# 将整体重复两次
pd.Series(['a', 'b', 'c']).repeat(repeats=2)
11.3.8 文本连接
# 文本序列
s = pd.Series(['x', 'y', 'z'], dtype="string")

# 默认无符号连接
s.str.cat()
# 'xyz'

# 用逗号连接
s.str.cat(sep=',')
# 'x,y,z'

# 包含空值的文本序列
t = pd.Series(['h', 'i', np.nan, 'k'], dtype="string")

# 用逗号连接
t.str.cat(sep=',')
# 'h,i,k'

# 用连字符
t.str.cat(sep=',', na_rep='-')
# 'h,i,-,k'

t.str.cat(sep=',', na_rep='j')
# 'h,i,j,k'

h = pd.Series(['b', 'd', 'a'],
              index=[1, 0, 2],
              dtype="string")

# 以左边的索引为准
s.str.cat(h)
s.str.cat(t, join='left')
# 以右边的索引为准
s.str.cat(h, join='right')
# 其他
s.str.cat(h, join='outer', na_rep='-')
s.str.cat(h, join='inner', na_rep='-')
11.3.9 文本查询
# 字符序列
s = pd.Series(['One', 'Two', 'Three'])
# 查询字符
s.str.findall('T')

# 区分大小写,不会查出内容
s.str.findall('ONE')
# 忽略大小写
import re
s.str.findall('ONE', flags=re.IGNORECASE)
# 包含o
s.str.findall('o')
# 以o结尾
s.str.findall('o$')
# 包含多个的会形成一个列表
s.str.findall('e')
s.str.find('One')
11.3.10 文本包含
# 原数据
s = pd.Series(['One', 'Two', 'Three', np.NaN])
# 是否包含检测
s.str.contains('o', regex=False)

# 名字包含A字母
df.loc[df.name.str.contains('A')]
# 包含字母A或者C
df.loc[df.name.str.contains('A|C')]
# 忽略大小写
import re
df.loc[df.name.str.contains('A|C', flags=re.IGNORECASE)]
# 包含数字
df.loc[df.name.str.contains('\d')]

# 原数据
s = pd.Series(['One', 'Two', 'Three', np.NaN])
s.str.startswith('O')
# 对空值的处理
s.str.startswith('O', na=False)
s.str.endswith('e')
s.str.endswith('e', na=False)

pd.Series(['1', '2', '3a', '3b', '03c'],
          dtype="string").str.match(r'[0-9][a-z]')
11.3.11 文本提取
(pd.Series(['a1', 'b2', 'c3'], dtype="string")
   .str
   .extract(r'([ab])(\d)', expand=True)
)

s.str.extract(r'([ab])?(\d)')

s = pd.Series(["a1a2", "b1b7", "c1"],
              index=["A", "B", "C"],
              dtype="string")
two_groups = '(?P<letter>[a-z])(?P<digit>[0-9])'
s.str.extract(two_groups, expand=True) # 单次匹配
s.str.extractall(two_groups)

11.3.12 提取虚拟变量
s = pd.Series(['a/b', 'b/c', np.nan, 'c'],
              dtype="string")

idx = pd.Index(['a/b', 'b/c', np.nan, 'c'])
idx.str.get_dummies(sep='/')

第12章 Pandas分类数据

12.1 分类数据

12.1.1 创建分类数据
# 构造数据
s = pd.Series(["x", "y", "z", "x"], dtype="category")

# 构造数据
df = pd.DataFrame({'A': list('xyzz'), 'B': list('aabc')}, dtype="category")

# 生成分箱序列
pd.Series(pd.cut(range(1, 10, 2), [0,4,6,10]))

12.1.2 pd.Categorical()
# 分类数据
pd.Categorical(["x", "y", "z", "x"], categories=["y", "z", "x"], ordered=True)

# 构建 Series
pd.Series(pd.Categorical(["x", "y", "z", "x"],
                         categories=["y", "z", "x"],
                         ordered=False)
         )

12.1.3 CategoricalDtype对象
from pandas.api.types import CategoricalDtype
CategoricalDtype(['a', 'b', 'c'])

# 类别指定CategoricalDtype对象
pd.Series(list('abcabc'), dtype=c)

12.1.4 类型转换
from pandas.api.types import CategoricalDtype

# 定义CategoricalDtype对象
c = CategoricalDtype(['A', 'B', 'C', 'D', 'E'])
# 应用到类型转换
df.team.astype(c)

12.2 分类的操作

12.2.1 修改分类
s = pd.Series(["a", "b", "c", "a"], dtype="category")
# 修改分类
s.cat.categories = ['x', 'y', 'z']
# 修改分类
s.cat.rename_categories({'a':'x', 'b':'y', 'c':'z'})
# 设置分类
s.cat.set_categories(["b", "c", "a"])
12.2.2 追加新分类
# 追加分类
s = s.cat.add_categories(['t'])
s.cat.categories
12.2.3 删除分类
# 删除分类
s = s.cat.remove_categories(['y'])
s = pd.Series(pd.Categorical(["a", "b", "a"],
                             categories=["a", "b", "c", "d"]))
12.2.4 顺序
s = pd.Series(["a", "b", "c", "a"], dtype="category")

# 查看分类
s.cat.categories

# 是否有序
s.cat.ordered

s = pd.Series(pd.Categorical(["a", "b", "c", "a"],
                             categories=["c", "b", "a"]))
s.cat.categories
s.cat.ordered

s = pd.Series(["a", "b", "c", "a"], dtype="category")

# 设置为有序
s.cat.as_ordered()

# 重新排序
s.cat.reorder_categories(['b', 'a', 'c'], ordered=True)

第13章 Pandas窗口计算

13.2 窗口操作

13.2.3 移动窗口使用
# 数据
df = pd.DataFrame(np.random.randn(30, 4),
                  index=pd.date_range('10/1/2020', periods=30),
                  columns=['A', 'B', 'C', 'D'])

# 每两天一个窗口,求平均数
df.rolling(2).mean()

# 每两天一个窗口,求平均数
df.rolling('2D').mean()

# 仅对A列进行窗口计算
df.rolling('2D', c)['A'].mean()
df.A.rolling('2D').mean() 

# 使用窗口函数,汉明窗
df.rolling(2, win_type='hamming').sum()

13.2.5 agg()
# 对窗口中的不同列使用不同的计算方法
df.rolling('2D').agg({'A':sum, 'B': np.std})

# 对同一列使用多个函数
df.A.rolling('2D').agg({'A_sum':sum, 'B_std': np.std})
13.2.6 apply()
# 对窗口求和再加1,最终求绝对值
df.A.rolling('2D').apply(lambda x: abs(sum(x)+1))

第14章 Pandas时序数据

14.1 固定时间

14.1.1 时间的表示
# 当前时间
datetime.now()

# 指定时间
datetime(2020, 11, 1, 19)

# 指定时间
datetime(year=2020, month=11, day=11)
datetime.datetime(2020, 11, 11, 0, 0)
14.1.2 创建时间点
import datetime
# 至少需要年、月、日
pd.Timestamp(datetime.datetime(2020, 6, 8))

# 指定时、分、秒
pd.Timestamp(datetime.datetime(2020, 6, 8, 16, 17, 18))

pd.Timestamp('2012-05-01')
pd.Timestamp('2017-01-01T12')
pd.Timestamp(2012, 5, 1)
pd.Timestamp(2017, 1, 1, 12)
pd.Timestamp(year=2017, month=1, day=1, hour=12)
pd.Timestamp(1513393355.5, unit='s') # 单位为秒
pd.Timestamp(1513393355, unit='s', tz='US/Pacific')
# 指定为北京时间
pd.Timestamp(1513393355, unit='s', tz='Asia/Shanghai')
pd.Timestamp('today')
pd.Timestamp('now')
pd.Timestamp('today').date() # 只取日期

# 昨天
pd.Timestamp('now')-pd.Timedelta(days=1)
# 明天
pd.Timestamp('now')+pd.Timedelta(days=1)
# 当月初,一日
pd.Timestamp('now').replace(day=1)
pd.to_datetime('now')
pd.Timestamp.min
pd.Timestamp.max
14.1.3 时间的属性
time = pd.Timestamp('now')
time.asm8 # 返回NumPy datetime64格式(以纳秒为单位)
time.dayofweek # 1(周几,周一为0)
time.dayofyear # 161(一年的第几天)
time.days_in_month # 30(当月有多少天)
time.daysinmonth # 30(同上)
time.freqstr # None(周期字符)
time.is_leap_year # True(是否闰年,公历的)
time.is_month_end # False(是否当月最后一天)
time.is_month_start # False(是否当月第一天)
time.is_quarter_end # False(是否当季最后一天)
time.is_quarter_start # False(是否当季第一天)
time.is_year_end # 是否当年最后一天
time.is_year_start # 是否当年第一天
time.quarter # 2(当前季度数)
# 如指定,会返回类似<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>
time.tz # None(当前时区别名)
time.week # 24(当年第几周)
time.weekofyear # 24(同上)
time.day # 9(日)
time.fold # 0
time.freq # None(频度周期)
time.hour # 16
time.microsecond # 890462
time.minute # 46
time.month # 6
time.nanosecond # 0
time.second # 59
time.tzinfo # None
time.value # 1591721219890462000
time.year # 2020
14.1.4 时间的方法
time = pd.Timestamp('now', tz='Asia/Shanghai')
# 转换为指定时区
time.astimezone('UTC')

# 转换单位,向上舍入
time.ceil('s') # 转为以秒为单位
time.ceil('ns') # 转为以纳秒为单位
time.ceil('d') # 保留日
time.ceil('h') # 保留时

# 转换单位,向下舍入
time.floor('h') # 保留时

# 类似四舍五入
time.round('h') # 保留时

# 返回星期名
time.day_name() # 'Tuesday'
# 月份名称
time.month_name() # 'June'

# 将时间戳规范化为午夜,保留tz信息
time.normalize()

# 将时间元素替换datetime.replace,可处理纳秒
time.replace(year=2019) # 年份换为2019年
time.replace(month=8) # 月份换为8月

# 转换为周期类型,将丢失时区
time.to_period(freq='h') # 周期为小时

# 转换为指定时区
time.tz_convert('UTC') # 转为UTC时间

# 本地化时区转换
time = pd.Timestamp('now')
time.tz_localize('Asia/Shanghai')
time.tz_localize(None) # 删除时区
14.1.5 时间缺失值
pd.Timestamp(pd.NaT)
pd.Timedelta(pd.NaT)
pd.Period(pd.NaT)
# 类似np.nan
pd.NaT == pd.NaT
# False
pd.NaT + pd.Timestamp('20201001')
# NaT
pd.NaT + pd.Timedelta('2 days')
# NaT
pd.Timedelta('2 days') - pd.NaT
# NaT

14.2 时长数据

14.2.1 创建时间差
# 两个固定时间相减
pd.Timestamp('2020-11-01 15') - pd.Timestamp('2020-11-01 14')

# 一天
pd.Timedelta('1 days')
pd.Timedelta('1 days 00:00:00')
pd.Timedelta('1 days 2 hours')
pd.Timedelta('-1 days 2 min 3us')
pd.Timedelta(days=5, seconds=10)
pd.Timedelta(minutes=3, seconds=2)

# 可以将指定分钟转换为天和小时
pd.Timedelta(minutes=3242)
# 一天
pd.Timedelta('1D')

# 两周
pd.Timedelta('2W')

# 一天零2小时3分钟4秒
pd.Timedelta('1D2H3M4S')

# 一天
pd.Timedelta(1, unit='d')

# 100秒
pd.Timedelta(100, unit='s')

# 4周
pd.Timedelta(4, unit='w')

import datetime
import numpy as np

# 一天零10分钟
pd.Timedelta(datetime.timedelta(days=1, minutes=10))

# 100纳秒
pd.Timedelta(np.timedelta64(100, 'ns'))

# 负值
pd.Timedelta('-1min')

# 空值,缺失值
pd.Timedelta('nan')
# NaT
pd.Timedelta('P0DT0H1M0S')
pd.Timedelta('P0DT0H0M0.000000123S')

# 两分钟
pd.Timedelta(pd.offsets.Minute(2))

# 3天
pd.Timedelta(pd.offsets.Day(3))
pd.to_timedelta(pd.offsets.Day(3))
pd.to_timedelta('15.5min')
pd.to_timedelta(124524564574835)
pd.Timedelta.min
pd.Timedelta.max
14.2.2 时长的加减
# 一天与5个小时相加
pd.Timedelta(pd.offsets.Day(1)) + pd.Timedelta(pd.offsets.Hour(5))

# 一天与5个小时相减
pd.Timedelta(pd.offsets.Day(1)) - pd.Timedelta(pd.offsets.Hour(5))

# 11月11日减去一天
pd.Timestamp('2020-11-11') - pd.Timedelta(pd.offsets.Day(1))

# # 11月11日加3周
pd.Timestamp('2020-11-11') + pd.Timedelta('3W')
14.2.3 时长的属性
tdt = pd.Timedelta('10 days 9 min 3 sec')
tdt.days # 10
tdt.seconds # 543
(-tds).days # -11
tdt.value # 864543000000000(时间戳)
14.2.4 时长索引

14.3 时间序列

14.3.1 时序索引
import datetime
import numpy as np

pd.to_datetime(['11/1/2020', # 类时间字符串
                np.datetime64('2020-11-02'), # NumPy的时间类型
                datetime.datetime(2020, 11, 3)]) # Python自带时间类型


# 默认频率为天
pd.date_range('2020-01-01', periods=10)
pd.date_range('2020-01-01', '2020-01-10') # 同上
pd.date_range(end='2020-01-10', periods=10) # 同上

# 频率为工作日
pd.bdate_range('2020-11-1', periods=10)

14.3.2 创建时序数据
# 生成时序索引
tidx = pd.date_range('2020-11-1', periods=10)
# 应用时序索引
s = pd.Series(range(len(tidx)), index=tidx)
pd.Series(tidx)

# 索引
tidx = pd.date_range('2020-11-1', periods=10)
# 应用索引生成DataFrame
df = pd.DataFrame({'A': range(len(tidx)), 'B': range(len(tidx))[::-1]}, index=tidx)

14.3.3 数据访问
idx = pd.date_range('1/1/2020', '12/1/2021', freq='H')
ts = pd.Series(np.random.randn(len(idx)), index=idx)

# 指定区间的
ts[5:10]

# 指定天,结果相同
ts['11/30/2020']
ts['2020-11-30']
ts['20201130']

# 指定时间点
ts[datetime.datetime(2020, 11, 30)]
ts[pd.Timestamp(2020, 11, 30)] 
ts[pd.Timestamp('2020-11-30')] 
ts[np.datetime64('2020-11-30')] 

ts['2021'] # 查询整个2021年的
ts['2021-6'] # 查询2021年6月的
ts['2021-6':'2021-10'] # 查询2021年6月到10月的
dft['2021-1':'2021-2-28 00:00:00'] # 精确时间
dft['2020-1-15':'2020-1-15 12:30:00']
dft2.loc['2020-01-05']
# 索引选择器
idx = pd.IndexSlice
dft2.loc[idx[:, '2020-01-05'], :]
# 带时区,原数据时区可能不是这个
df['2020-01-01 12:00:00+04:00':'2020-01-01 13:00:00+04:00']

# 时间粒度(频率)
ts.index.resolution

# 给定开始时间和结束时间来截取部分时间
ts.truncate(before='2020-11-10 11:20', after='2020-12')

14.3.4 类型转换
s = pd.Series(['2020-11-01 01:10', '2020-11-11 11:10', '2020-11-30 20:10'])

# 转为时间类型
s.astype('datetime64[ns]')

# 转为时间类型,指定频率为天
s.astype('datetime64[D]')

# 转为时间类型,指定时区为北京时间
s.astype('datetime64[ns, Asia/Shanghai]')

# 转为时间类型
pd.to_datetime(s)

df = pd.DataFrame({'year': [2020, 2020, 2020],
                   'month': [10, 11, 12],
                   'day': [10, 11, 12]})

s = pd.Series(['2020-11-01 01:10', '2020-11-11 11:10', None])
pd.to_datetime(s)
pd.to_datetime(['2020/11/11', '2020.12.12'])
pd.to_datetime(['1-10-2020 10:00'], dayfirst=True) # 按日期在前解析

# 转为时间序列索引,自动推断频率
pd.DatetimeIndex(['20201101', '20201102', '20201103'], freq='infer')
pd.to_datetime('2020/11/12')
pd.Timestamp('2020/11/12')
14.3.5 按格式转换
# 不规则格式转换时间
pd.to_datetime('2020_11_11', format='%Y_%m_%d', errors='ignore')

# 可以让系统自己推断时间格式
pd.to_datetime('20200101', infer_datetime_format=True, errors='ignore')

# 将errors参数设置为coerce,将不会忽略错误,返回空值
pd.to_datetime('20200101', format='%Y%m%d', errors='coerce')

# 列转为字符串,再转为时间类型
pd.to_datetime(df.d.astype(str), format='%m/%d/%Y')

# 其他
pd.to_datetime('2020/11/12', format='%Y/%m/%d')

pd.to_datetime('01-10-2020 00:00', format='%d-%m-%Y %H:%M')

# 对时间戳进行转换,需要给出时间单位,一般为秒
pd.to_datetime(1490195805, unit='s')
pd.to_datetime(1490195805433502912, unit='ns')

pd.to_datetime([10, 11, 12, 15], unit='D', origin=pd.Timestamp('2020-11-01'))
14.3.6 时间访问器.dt
s = pd.Series(pd.date_range('2020-11-01', periods=5, freq='d'))

# 时间访问器操作
s.dt.date
s.dt.time
s.dt.timetz

# 以下为时间各成分的值
s.dt.year
s.dt.month
s.dt.day
s.dt.hour
s.dt.minute
s.dt.second
s.dt.microsecond
s.dt.nanosecond

# 以下为与周、月、年相关的属性
s.dt.week
s.dt.weekofyear
s.dt.dayofweek
s.dt.weekday
s.dt.dayofyear # 一年中的第几天
s.dt.quarter # 季度数
s.dt.is_month_start # 是否月第一天
s.dt.is_month_end # 是否月最后一天
s.dt.is_quarter_start # 是否季度第一天
s.dt.is_quarter_end # 是否季度最后一天
s.dt.is_year_start # 是否年第一天
s.dt.is_year_end # 是否年最后一天
s.dt.is_leap_year # 是否闰年
s.dt.daysinmonth # 当月有多少天
s.dt.days_in_month # 同上

s.dt.tz # 时区
s.dt.freq # 频率

# 以下为转换方法
s.dt.to_period
s.dt.to_pydatetime
s.dt.tz_localize
s.dt.tz_convert
s.dt.normalize
s.dt.strftime

s.dt.round(freq='D') # 类似四舍五入
s.dt.floor(freq='D') # 向下舍入为天
s.dt.ceil(freq='D') # 向上舍入为天

s.dt.month_name # 月份名称
s.dt.day_name # 星期几的名称
s.dt.start_time # 开始时间
s.dt.end_time # 结束时间
s.dt.days # 天数
s.dt.seconds # 秒
s.dt.microseconds # 毫秒
s.dt.nanoseconds # 纳秒
s.dt.components # 各时间成分的值
s.dt.to_pytimedelta # 转为Python时间格式
s.dt.total_seconds # 总秒数

# 个别用法举例
# 将时间转为UTC时间,再转为美国东部时间
s.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
# 输出时间显示格式
s.dt.strftime('%Y/%m/%d')
14.3.7 时长数据访问器
ts = pd.Series(pd.to_timedelta(np.arange(5), unit='hour'))
14.3.8 时序数据移动
rng = pd.date_range('2020-11-01', '2020-11-04')
ts = pd.Series(range(len(rng)), index=rng)
# 向上移动一个工作日,11-01是周日
ts.shift(-1, freq='B')

14.3.9 频率转换
rng = pd.date_range('2020-11-01', '2020-12-01')
ts = pd.Series(range(len(rng)), index=rng)

# 频率转为12小时
ts.asfreq(pd.offsets.Hour(12))

# 对缺失值进行填充
ts.asfreq(freq='12h', fill_value=0)

14.4 时间偏移

14.4.1 DateOffset对象
# 生成一个指定的时间,芬兰赫尔辛基时间执行夏令时
t = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki')

t + pd.Timedelta(days=1) # 增加一个自然天
t + pd.DateOffset(days=1) # 增加一个时间偏移天

# 定义一个日期
d = pd.Timestamp('2020-10-30')
d.day_name() # 'Friday'

# 定义2个工作日时间偏移变量
two_business_days = 2 * pd.offsets.BDay()

# 增加两个工作日
two_business_days.apply(d)
d + two_business_days # 同上

# 取增加两个工作日后的星期
(d + two_business_days).day_name()

from pandas.tseries.offsets import DateOffset
ts = pd.Timestamp('2020-01-01 09:10:11')
ts + DateOffset(months=3)
ts + DateOffset(hours=2)
ts + DateOffset()
14.4.3 移动偏移
ts = pd.Timestamp('2020-06-06 00:00:00')
ts.day_name()

# 定义一个工作小时偏移,默认是周一到周五9~17点,我们从10点开始
offset = pd.offsets.BusinessHour(start='10:00')

# 向前偏移一个工作小时,是一个周一,跳过了周日
offset.rollforward(ts)

# 向前偏移至最近的工作日,小时也会增加
ts + offset

# 向后偏移,会在周五下班前的一个小时
offset.rollback(ts)

ts - pd.offsets.Day(1) # 昨日
ts - pd.offsets.Day(2) # 前日
ts - pd.offsets.Week(weekday=0) - pd.offsets.Day(14) # 上周一
ts - pd.offsets.MonthEnd() - pd.offsets.MonthBegin() # 上月一日

offset.rollback(ts).normalize()
14.4.4 应用偏移
ts = pd.Timestamp('2020-06-01 09:00')
day = pd.offsets.Day() # 定义偏移对象
day.apply(ts) # 将偏移对象应用到时间上
day.apply(ts).normalize() # 标准化/归一化
ts = pd.Timestamp('2020-06-01 22:00')
hour = pd.offsets.Hour()
hour.apply(ts)
hour.apply(ts).normalize()
hour.apply(pd.Timestamp("2014-01-01 23:30")).normalize()
14.4.5 偏移参数
import datetime
d = datetime.datetime(2020, 6, 1, 9, 0)
d + pd.offsets.Week() # 偏移一周
d + pd.offsets.Week(weekday=4) # 偏移4周中的日期

# 取一周第几天
(d + pd.offsets.Week(weekday=4)).weekday()
d - pd.offsets.Week() # 向后一周
d + pd.offsets.Week(normalize=True)
d - pd.offsets.Week(normalize=True)
d + pd.offsets.YearEnd()
14.4.6 相关查询
i = pd.date_range('2018-04-09', periods=4, freq='2D')
ts = pd.DataFrame({'A': [1, 2, 3, 4]}, index=i)
# 指定时间
ts.at_time('12:00')
ts.between_time('0:15', '0:45')

14.4.7 与时序的计算
rng = pd.date_range('2020-01-01', '2020-01-03')
s = pd.Series(rng)
rng
# DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03'], dtype=
    'datetime64[ns]', freq='D')
s - pd.offsets.Day(2)

14.4.8 锚定偏移
pd.Timestamp('2020-01-02') + pd.offsets.MonthBegin(n=1)
pd.Timestamp('2020-01-02') + pd.offsets.MonthEnd(n=1)

14.4.9 自定义工作时间
import datetime

weekmask_egypt = 'Sun Mon Tue Wed Thu'

# 定义出五一劳动节的日期,因为放假
holidays = ['2018-05-01',
            datetime.datetime(2019, 5, 1),
            np.datetime64('2020-05-01')]

# 自定义工作日中传入休假日期,一个正常星期工作日的顺序
bday_egypt = pd.offsets.CustomBusinessDay(holidays=holidays,
                                          weekmask=weekmask_egypt)

# 指定一个日期
dt = datetime.datetime(2020, 4, 30)
# 偏移两个工作日,跳过了休假日
dt + 2 * bday_egypt

# 输出时序及星期几
idx = pd.date_range(dt, periods=5, freq=bday_egypt)
pd.Series(idx.weekday+1, index=idx)

bh = pd.offsets.BusinessHour()

# 2020-08-01是周五
pd.Timestamp('2020-08-01 10:00').weekday()

# 增加一个工作小时
pd.Timestamp('2020-08-01 10:00') + bh

# 一旦计算就等于上班了,等同于pd.Timestamp('2020-08-01 09:00') + bh
pd.Timestamp('2020-08-01 08:00') + bh

# 计算后已经下班了,就移到下一个工作小时(跳过周末)
pd.Timestamp('2020-08-01 16:00') + bh
Out[205]: Timestamp('2020-08-04 09:00:00')

# 同上逻辑,移动一个工作小时
pd.Timestamp('2020-08-01 16:30') + bh

# 偏移两个工作小时
pd.Timestamp('2020-08-01 10:00') + pd.offsets.BusinessHour(2)

# 减去3个工作小时
pd.Timestamp('2020-08-01 10:00') + pd.offsets.BusinessHour(-3)

# 11点开始上班
bh = pd.offsets.BusinessHour(start='11:00', end=datetime.time(20, 0))

pd.Timestamp('2020-08-01 13:00') + bh

pd.Timestamp('2020-08-01 09:00') + bh

pd.Timestamp('2020-08-01 18:00') + bh

bh = pd.offsets.BusinessHour(start='17:00', end='09:00')

pd.Timestamp('2014-08-01 17:00') + bh

pd.Timestamp('2014-08-01 23:00') + bh

# 尽管2014年8月2日是周六,
# 但因为工作时间从周五开始,因此也有效
pd.Timestamp('2014-08-02 04:00') + bh

# 虽然2014年8月4日是周一,
# 但开始时间是周日,超出了工作时间
pd.Timestamp('2014-08-04 04:00') + bh

14.5 时间段

14.5.1 Period对象
# 创建一个时间段(年)
pd.Period('2020')
# Period('2020', 'A-DEC')

# 创建一个时间段(季度)
pd.Period('2020Q4')
# Period('2020Q4', 'Q-DEC')

# 2020-01-01全天的时间段
pd.Period(year=2020, freq='D')

# 一周
pd.Period('20201101', freq='W')

# 默认周期,对应到最细粒度——分钟
pd.Period('2020-11-11 23:00')

# 指定周期
pd.Period('2020-11-11 23:00', 'D')
14.5.2 属性方法
# 定义时间段
p = pd.Period('2020Q4')

# 开始与结束时间
p.start_time
p.end_time

p.asfreq('D') # 将频率转换为天
p.asfreq('D', how='start') # 以起始时间为准

p.freq # <QuarterEnd: startingMonth=12>(时间偏移对象)
p.freqstr # 'Q-DEC'(时间偏移别名)
p.is_leap_year # True(是否闰年)
p.to_timestamp() # 

# 以下日期取时间段内最后一天
p.day # 1(日)
p.dayofweek # 3(周四)
p.dayofyear # 366(一年第几天)
p.hour # 0(小时)
p.week
p.minute
p.second
p.month
p.quarter # 4
p.qyear # 2020(财年)
p.year
p.days_in_month # 31(当月第几天)
p.daysinmonth # 31(当月共多少天)
p.strftime('%Y年%m月') # '2020年12月'(格式化时间)
14.5.3 时间段的计算
# 在2020Q4上增加一个周期
pd.Period('2020Q4') + 1
# 在2020Q4上减少一个周期
pd.Period('2020Q4') - 1
# 增加一小时
pd.Period('20200101 15') + pd.offsets.Hour(1)
# 增加10天
pd.Period('20200101') + pd.offsets.Day(10)
pd.Period('20200101 14') + pd.offsets.Day(10)
pd.Period('20200101 14') + pd.offsets.Minute(10)
pd.Period('2020 10') + pd.offsets.MonthEnd(3)
pd.Period('20200101 14') + pd.Timedelta('1 days')
pd.Period('20200101 14') + pd.Timedelta('1 seconds')
# IncompatibleFrequency: Input cannot be converted to Period(freq=H)
pd.Period('20200101 14') - pd.Period('20200101 10')
pd.Period('2020Q4') - pd.Period('2020Q1')
14.5.4 时间段索引
# 生成时间段索引对象
pd.period_range('2020-11-01 10:00', periods=10, freq='H')

# 指定开始和结束时间
pd.period_range('2020Q1', '2021Q4', freq='Q-NOV')

# 通过传入时间段对象来定义
pd.period_range(start=pd.Period('2020Q1', freq='Q'),
                end=pd.Period('2021Q2', freq='Q'), freq='M')

pd.Series(pd.period_range('2020Q1', '2021Q4', freq='Q-NOV'))

14.5.5 数据查询
s = pd.Series(1, index=pd.period_range('2020-10-01 10:00', '2021-10-01 10:00', freq='H'))
14.5.6 相关类型转换
ts = pd.date_range('20201101', periods=100)
ts = pd.period_range('2020-11', periods=100, freq='M')
# 频率从月转为季度
ts.astype('period[Q]')

14.6 时间操作

14.6.1 时区转换
import pytz

print(pytz.common_timezones)
print(pytz.timezone)

ts = pd.date_range('11/11/2020 00:00', periods=10, freq='D')
ts.tz is None
# True

pd.date_range('2020-01-01', periods=10, freq='D', tz='Asia/Shanghai')
pd.Timestamp('2020-01-01', tz='Asia/Shanghai')

# 使用pytz支持
rng_pytz = pd.date_range('11/11/2020 00:00', periods=3,
                         freq='D', tz='Europe/London')
rng_pytz.tz

# 使用dateutil支持
rng_dateutil = pd.date_range('11/11/2020 00:00', periods=3, freq='D')
# 转为伦敦所在的时区
rng_dateutil = rng_dateutil.tz_localize('dateutil/Europe/London')
rng_dateutil.tz

# 使用dateutil指定为UTC时间
rng_utc = pd.date_range('11/11/2020 00:00', periods=3,
                        freq='D', tz=dateutil.tz.tzutc())
rng_utc.tz


rng_utc = pd.date_range('11/11/2020 00:00', periods=3,
                        freq='D', tz=datetime.timezone.utc)


rng_pytz.tz_convert('US/Eastern')

s_naive.dt.tz_localize('UTC').dt.tz_convert('US/Eastern')
s_naive.astype('datetime64[ns, US/Eastern]')
s_aware.to_numpy(dtype='datetime64[ns]')
14.6.2 时间的格式化
# 解析时间格式
pd.to_datetime('2020*11*12', format='%Y*%m*%d')
# 输出的时间格式
pd.Timestamp('now').strftime('%Y年%m月%d日')
# '2020年11月05日'
14.6.3 时间重采样
idx = pd.date_range('2020-01-01', periods=500, freq='Min')
ts = pd.Series(range(len(idx)), index=idx)
ts.resample('5Min').mean() # 平均
ts.resample('5Min').max() # 最大值

# 两小时频率的美国线
ts.resample('2h').ohlc()
ts.resample('2h', closed='left').mean()
ts.resample('5Min').mean()  # 默认 label='left'
ts.resample('5Min', label='right').mean()
14.6.4 上采样
ts.head(3).resample('30S').asfreq()
ts.head(3).resample('30S').ffill()

14.6.5 重采样聚合
df = pd.DataFrame(np.random.randn(1000, 3),
                  index=pd.date_range('1/1/2020', freq='S', periods=1000),
                  columns=['A', 'B', 'C'])

# 生成Resampler重采样对象
r = df.resample('3T')
r.mean()

r['A'].agg([np.sum, np.mean, np.std])
r.agg([np.sum, np.mean]) # 每个列
# 不同的聚合方式
r.agg({'A': np.sum,
       'B': lambda x: np.std(x, ddof=1)})
# 用字符指定
r.agg({'A': 'sum', 'B': 'std'})
r.agg({'A': ['sum', 'std'], 'B': ['mean', 'std']})

# date是一个普通列
df.resample('M', on='date').sum()
df.resample('M', level='d').sum() # 多层索引

# r 是重采样对象
for name, group in r:
    print("Group: ", name)
    print("-" * 20)
    print(group, end="\n\n")
14.6.6 时间类型间转换
pd.date_range('1/1/2020', periods=5)
pd.period_range('1/1/2020', periods=5)

14.6.7 超出时间戳范围时间
# 定义一个超限时间周期
pd.period_range('1111-01-01', '8888-01-01', freq='D')

(pd.Series([123_1111, 2008_10_01, 8888_12_12])
 # 将整型转为时间周期类型
 .apply(lambda x: pd.Period(year=x // 10000,
                            month=x // 100 % 100,
                            day=x % 100,
                            freq='D')
       )
)
14.6.8 区间间隔
# Interval对象构建
pd.Interval(left=0, right=5, closed='right')
# 4 是否在1~10之间
4 in pd.Interval(1,10)
# 10 是否在1~9之间
10 in pd.Interval(1,10,closed='left')
# False
iv = pd.Interval(left=0, right=5)

# 可以检查元素是否属于它
3.5 in iv # True
5.5 in iv # False

# 可以测试边界值
# closed ='right',所以0 < x <= 5
0 in iv # False
5 in iv # True
0.0001 in iv # True

# 定义一个2020年的区间
year_2020 = pd.Interval(pd.Timestamp('2020-01-01 00:00:00'),
                        pd.Timestamp('2021-01-01 00:00:00'),
                        closed='left')

# 检查指定时间是否在2020年区间里
pd.Timestamp('2020-01-01 00:00') in year_2020
# True

# 2020年时间区间的长度
year_2020.length

# 定义一个时长区间,3秒到1天
time_deltas = pd.Interval(pd.Timedelta('3 seconds'),
                          pd.Timedelta('1 days'),
                          closed='both')

# 5分钟是否在时间区间里
pd.Timedelta('5 minutes') in time_deltas

# 时长区间长度
time_deltas.length

# 区间闭合之处
iv.closed # 'right'
# 检查间隔是否在左侧关闭
iv.closed_left # False
# 检查间隔是否在右侧关闭
iv.closed_right # True
# 间隔是否为空,表示该间隔不包含任何点
iv.is_empty # False
# 间隔的左边界
iv.left # 0
# 间隔的右边界
iv.right # 5
# 间隔的长度
iv.length # 5
# 间隔的中点
iv.mid # 2.5
# 间隔是否在左侧为开区间
iv.open_left # True
# 间隔是否在右侧为开区间
iv.open_right # False

pd.Interval(0, 1, closed='right').is_empty # False

# 不包含任何点的间隔为空
pd.Interval(0, 0, closed='right').is_empty # True
pd.Interval(0, 0, closed='left').is_empty # True
pd.Interval(0, 0, closed='neither').is_empty # True

# 包含单个点的间隔不为空
pd.Interval(0, 0, closed='both').is_empty # False
# 一个IntervalArray或IntervalIndex返回一个布尔ndarray
# 它在位置上指示Interval是否为空
ivs = [pd.Interval(0, 0, closed='neither'),
       pd.Interval(1, 2, closed='neither')]
pd.arrays.IntervalArray(ivs).is_empty


# 缺失值不为空
ivs = [pd.Interval(0, 0, closed='neither'), np.nan]
pd.IntervalIndex(ivs).is_empty


i1 = pd.Interval(0, 2)
i2 = pd.Interval(1, 3)
i1.overlaps(i2) # True

i3 = pd.Interval(4, 5)
i1.overlaps(i3) # False

i4 = pd.Interval(0, 1, closed='both')
i5 = pd.Interval(1, 2, closed='both')
i4.overlaps(i5) # True

i6 = pd.Interval(1, 2, closed='neither')
i4.overlaps(i6) # False

shifted_iv = iv + 3
shifted_iv

extended_iv = iv * 10.0
extended_iv

第16章 Pandas可视化

16.1 plot()方法

16.1.1 plot()概述
# DataFrame调用
df.plot()
# Series调用
s.plot()
df.plot.line() # 折线的全写方式
df.plot.bar() # 柱状图
df.plot.barh() # 横向柱状图(条形图)
df.plot.hist() # 直方图
df.plot.box() # 箱形图
df.plot.kde() # 核密度估计图
df.plot.density() # 同df.plot.kde()
df.plot.area() # 面积图
df.plot.pie() # 饼图
df.plot.scatter() # 散点图
df.plot.hexbin() # 六边形箱体图,或简称六边形图
16.1.2 plot()基础方法
ts = pd.Series(list(range(5))+list(range(5)),
               index=pd.date_range('1/1/2020', periods=10))

# 绘图
ts.plot()

df = pd.DataFrame(np.random.randn(6, 4),
                  index=pd.date_range('1/1/2020', periods=6),
                  columns=list('ABCD'))
df = abs(df)

# 绘图
df.plot()
df = pd.DataFrame(np.random.randn(50, 2), columns=['B', 'C']).cumsum()
df['A'] = pd.Series(list(range(len(df))))
df.plot(x='A', y='B') # 指定x和y轴的内容

# y轴指定两列
df.plot(x='A', y=['B','C'])
16.1.3 图形类型
df.plot(kind='pie') # 其他的名称和上文相同
s.plot(kind='pie')
16.1.4 x轴和y轴
# 可以不写参数名,直接按位置传入
df[:5].plot('name', 'Q1')
df[:5].plot.bar('name', ['Q1', 'Q2'])
df[:5].plot.barh(x='name', y='Q4')
df[:5].plot.area('name', ['Q1', 'Q2'])
df[:5].plot.scatter('name', 'Q3')
16.1.5 图形标题
# 指定标题
df.head(10).plot.bar(title='前十位学生成绩分布图')
16.1.6 字体大小
# 指定轴上的字体大小
df.set_index('name')[:5].plot(fontsize=20)
16.1.7 线条样式
df[:5].plot(style=':') # 虚线
df[:5].plot(style='-.') # 虚实相间
df[:5].plot(style='--') # 长虚线
df[:5].plot(style='-') # 实线(默认)
df[:5].plot(style='.') # 点
df[:5].plot(style='*-') # 实线,数值为星星
df[:5].plot(style='^-') # 实线,数值为三角形

# 指定线条样式
df.set_index('name').head().plot(style=[':', '--', '.-', '*-'])
16.1.8 背景辅助线
# 增加背景辅助线
df.set_index('name').head().plot(grid=True)
16.1.9 图例
# 不显示图例
df.set_index('name').head().plot(legend=False)

# 将图例倒排
df.set_index('name').head().plot(legend='reverse')
16.1.10 图形大小
# 定义图形大小
df.set_index('name').head().plot.bar(figsize=(10.5,5))

import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (15.0, 8.0) # 固定显示大小
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐