一、Pandas介绍

pandas 是基于NumPy 的一种工具,该工具是为解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。pandas提供了大量能使我们快速便捷地处理数据的函数和方法。你很快就会发现,它是使Python成为强大而高效的数据分析环境的重要因素之一。

Pandas [1]  是python的一个数据分析包,最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发team继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来,因此,pandas为时间序列分析提供了很好的支持。 Pandas的名称来自于面板数据(panel data)和python数据分析(data analysis)。panel data是经济学中关于多维数据集的一个术语,在Pandas中也提供了panel的数据类型。

二、Pandas读取文件

pandas 需要读取表格类型的数据 ,然后进行分析
csv tsv txt 用逗号分隔、tab分割的线文本文件 pd.read_csv
excel 微软xls或xlsx文件 pd.read_excel
mysql 关系型数据库存表 pd.read_sql

import pandas as pd
import pymysql

# 1、读取txt文件
fpath1="./read_csv.csv"
# # 读取数据
csv = pd.read_csv(fpath1)
# # 查看前几行
print(csv.head())
# # 查看数据的形状,返回(行数,列数)
print(csv.shape)
# # 查看列名
print(csv.columns)
# # 查看索引
print(csv.index)
# # 查看每列的数据类型
print(csv.dtypes)
# '''
#  读取txt文件,自定义分隔符、列名
# '''
csv1 = pd.read_csv(
    fpath1,
    sep="\t",
    header=None,
    names=['id','name','num']
)
print(csv1)


# 2、读取excel文件
fpath2="./my.xlsx"
xlsx1 = pd.read_excel(fpath2)
print(xlsx1)

# 3、读取MySql数据库
# (1)连接数据库
conn = pymysql.connect(
    host='192.168.1.17',
    user='root',
    passwd='123456',
    database='mysql',
    charset='utf8'
)
# # (2)查询数据库
mysql_page=pd.read_sql("SELECT * FROM user",conn)
print(mysql_page)</pre>

三、Pandas数据结构

Pandas数据结构:
DataFrame:二维数据,整个表格,多行多列
df.index:行
df.columns:列

Series:一维数据,一行或一列,二维降维到一维

import pandas as pd
import numpy as np


"""
1、Series,是一种类似于一维数组的对象,它一组数据(不同数据类型)以及一组与之相关的数据标签
    (即索引)组成。
    1.1 仅有数据列表即可产生最简单的Series
"""

s1 = pd.Series([1,'a',3.3,8])
# 左侧为索引,右侧是数据
print(s1)

# 获取索引
print(s1.index)

# 获取数据
print(s1.values)

"""
    1.2 创建一个具有标签索引的Series
"""
# 指定索引
s2=pd.Series([1,'b',3.3,8],index=['a','b','c','d'])
print(s2)
# 获取索引
print(s2.index)

"""
    1.3 使用Python字典创建Series
"""
sdata={'0id':3000,'1id':303.9,'2id':4990,'3id':8903}
s3=pd.Series(sdata)
print(s3)

"""
    1.4 根据标签索引查询数据
"""
print(s2['a'])
print(type(s2['c']))

print(s2[['a','b']])

print(type(s2[['a','b']]))

"""
2、DataFrame
    DataFrame是一个表格型的数据结构
        每列可以是不同的值类型(数值、字符串、布尔值等)
        既有行索引index,也有列索引columns
        可以被看做由Series组成的字典
    创建dataframe最常用的方法,见02节读取纯文本文件、excel、mysql数据库
    
"""
    # 2.1 根据多个字典序列创建DataFrame
data={
    'state':['haha','haha','haha','hello','hello'],
    'year':[2000,2001,2002,2003,2004],
    'pop':[1.5,1.7,1.9,3.2,4.8]
}
df=pd.DataFrame(data)
print(df)
# 查看类型
print(df.dtypes)

# 查看列索引
print(df.columns)

# 查看行索引
print(df.index)

"""
3、从DataFrame中查询出Series
    如果只查询一列、一列,返回的是pd.Series
    如果查询多行、多列,返回的是pd.DataFrame
"""
# 3.1 查询一列,结果是一个pd.Series
print(df['year'])
print(type(df['year']))

# 3.2 查询多列,结果是一个pd.DataFrame
print(df[['year','pop']])
print(type(df[['year','pop']]))

# 3.3 查询一行,结果是一个pd.Series
print(df.loc[1])
print(type(df.loc[1]))

# 3.4 查询多行,结果是一个pd.DataFrame
print(df.loc[1:3])
print(type(df.loc[1:3]))

四、Pandas数据查询

Pandas 数据查询 的五种方法:数值、列表、区间、条件、函数

Pandas查询数据的几种方法:
1、df.loc方法:根据行、列的标签值查询
2、df.iloc方法:根据行、列的数字位置查询
3、df.where方法
4、df.query方法
.loc既能查询 ,又能覆盖写入,强烈推荐

Pandas使用df.loc查询数据的方法
1、使用单个label值查询数据
2、使用值列表批量查询
3、使用数值区间进行范围查询
4、使用条件表达式查询
5、调用函数查询

注意:
以上查询方法,既适用于行,也适用于列
注意观察降维 DataFrame > Series > 值

import pandas as pd

""" 0、读取数据 """

df =pd.read_excel("./2020.xlsx")
print(df.head())

# 设定索引为日期
df.set_index('日期',inplace=True)
print(df.index)
# 重新查询,发现索引变为 日期
print(df.head())
print(df.dtypes)
# 替换数据,把 "bWendu" 列下的所有 "C" 替换为 "",类型为'int32'
# df.loc[:,"bWendu"] = df["bWendu"].str.replace("C","").astype('int32')

"""  1、使用单个label值查询数据 """
# 行 或 列,都可以只传入单个值,实现精确匹配
# 得到单个值
print(df.loc['2020-01-21',"单位"])

# 得到一个Series
print(df.loc['2020-01-21',["单位","品名","规格"]])

""" 2、使用值列表批量查询 """
# 得到Series
print(df.loc[['2020-01-21','2020-01-22','2020-01-23'],'单位'])

# 得到DataFrame
print(df.loc[['2020-01-21','2020-01-22','2020-01-23'],["单位","品名","规格"]])

""" 3、使用数值区间进行范围查询 """
# 注意:区间包含开始,也包含结束
# 行 index按区间
# print(df.loc['2020-01-02':'2020-01-15','单位'])

# 列 index按区间
print(df.loc['2020-01-21','品名':'单位'])

# 行和列都按区间查询
print(df.loc["2020-01-02": "2020-01-09", "品名":"单位"])

""" 4、使用条件表达式查询 """
# bool列表的长度得等于行数或者列数
# 简单条件查询,最小数量为10的列表
print(df.loc[df["数量"] < 10, :])
# 观察一下这里的boolean条件
print(df["数量"] < 10)

# 复杂查询:组合条件用 & 符号合并,每个条件判断都得带括号
print(df.loc[(df["数量"]<=500) & (df["数量"]>=100)& (df["品名"] == "30/70混合重组人胰岛素注射液(甘舒霖30R(原:甘舒霖30R笔芯))"), :])
print((df["数量"]<=500) & (df["数量"]>=100)& (df["品名"] == "30/70混合重组人胰岛素注射液(甘舒霖30R(原:甘舒霖30R笔芯))"))

''' 5、调用函数查询 '''
# 直接写lambda表达式
print(df.loc[lambda df:(df["数量"]<=500) & (df["数量"]>=100),:])
print((df["数量"]<=500) & (df["数量"]>=100))

# 自定义函数,查询
# 函数式编程的本质:函数自身可以像变量一样传递
def query_my_data(df):
    return df.index.str.startswith('2020-01') & df["数量"]==100
print(df.loc[query_my_data,:])

五、Pandas新增数据列

直接赋值、apply、assign、分条件赋值

在进行数据分析时,经常需要按照一定条件创建新的数据列,然后进行进一步分析。
1、直接赋值

2、df.apply方法

3、df.assign方法

4、按条件选择分组分别赋值

import pandas as pd

# 0、 读取csv数据到dataframe
fpath = "./beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)
print(df.head())

# 1、 直接赋值
# 替换掉温度的后缀 ℃ ,变成数字类型------> 修改列
df.loc[:,"bWendu"] = df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:,"yWendu"] = df["yWendu"].str.replace("℃","").astype("int32")

# 计算温差 ------->  新增列
# 注意:df["bWendu"]其实是一个Series,后面的减法返回的是Series
df.loc[:,"wencha"] = df["bWendu"] - df["yWendu"]
print(df.head())

# 2、 df.apply方法
"""
    Apply a function along an axis of the DataFrame.
    Object passed to the function are Series objects whose index is either the DataFrame's
 index (axis=0) or the DataFrame's columns (axis=1).
 实例:添加一列温度类型:
    1、如果最高温度大于33度就是高温
    2、低于-10度是低温
    3、否则常温   
"""
def get_wendu_type(x):
    if x["bWendu"] > 33:
        return "高温"
    if x["yWendu"] < -10:
        return "低温"
    return "常温"

# 注意: 需要设置axis == 1,这是Series的index是columns
df.loc[:,"wendu_type"] = df.apply(get_wendu_type,axis= 1)
print(df.head())
# 查看温度类型的计数
print(df["wendu_type"].value_counts())

# 3、df.assign方法
"""
    Assign new columns to a DataFrame.
    Returns a new object with all original columns in addition to new ones.
实例:将温度从摄氏度变成华氏度
"""
# 可以同时添加多个新的列
df.assign(
    # 摄氏转华氏
    yWendu_huashi=lambda x: x["yWendu"] * 9 / 5 + 32,
    bWendu_huashi=lambda x: x["bWendu"] * 9 / 5 + 32
)
print(df.head())

# 4、按条件选择分组分别赋值
"""
    按条件先选择数据,然后对这部分数据赋值新列
    实例:高低温差大于10度,则认为温差大
"""
# 先创建空列(这是第一种创建新列的方法)
df["wencha_type"] = ""
df.loc[df["bWendu"] - df["yWendu"] > 10,"wencha_type"] = "温差大"
df.loc[df["bWendu"] - df["yWendu"] < 10 ,"wencha_type"] = "温差正常"
print(df.head())

print(df["wencha_type"].value_counts())

六、Pandas 数据统计函数 1、汇总类统计 2、唯一去重和按值计数 3、相关系数和协方差

import pandas as pd

# 0、读取csv数据
fpath = "./beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)
# print(df.head(3))

# 替换掉温度的后缀 ℃ ,变成数字类型------> 修改列
df.loc[:,"bWendu"] = df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:,"yWendu"] = df["yWendu"].str.replace("℃","").astype("int32")

# 1、汇总类统计
df.describe()   # 一下子提取所有数字列统计结果
print(df.describe())

# 查看单个列Series的数据
print(df["bWendu"].mean())  # 平均温度
print(df["bWendu"].max())   # 最高温度
print(df["bWendu"].min())   # 最低温度

# 2、 唯一去重和按值计数
# 2.1 唯一去重
# 一般不用于数值列,而是枚举、分类列
print(df["fengxiang"].unique())
print(df["tianqi"].unique())
print(df["fengli"].unique())

# 2.2 按值计数
print(df["fengxiang"].value_counts())
print(df["tianqi"].value_counts())
print(df["fengli"].value_counts())

# 3、相关系数和协方差
"""
用途:(超级厉害)
    1、两只股票,是不是同涨同跌?程度多大?正相关还是负相关?
    2、产品销量的波动,跟哪些因素正相关、负相关、程度有多大?
来自知乎,对于两个变量x y:
    1、协方差:衡量同向反向程度,如果协方差为正,说明X、Y同向变化,协方差越大,说明同向程度越高;
    如果协方差为负,说明X,Y反向运动,协方差越小说明反向程度越高。
    2、相关系数:衡量相似度程度,当他们的相关系统为1时,说明两个变量变化时的正向相似度最大,
    当相关系数为-1时,说明两个变量变化的反向相似度最大。
"""
# 协方差矩阵
print(df.cov())

# 相关系数矩阵
print(df.corr())

# 单独查看空气质量和最高温度的相关系数
print(df["aqi"].corr(df["bWendu"]))
print(df["aqi"].corr(df["yWendu"]))

# 空气质量和温差的相关系数
print(df["aqi"].corr(df["bWendu"] - df["yWendu"]))

""" !! 这就是特征工程对于机器学习重要性的一个盒子!!"""

七、Pandas对缺失值的处理

1、isnull和notnull:检测是否是空值,可用于df和series
2、dropna:丢弃、删除缺失值
1)axis:删除行还是列,{0 or ‘index’, 1 or ‘columns’},default 0
2) how:如果等于any则任何值为空都删除,如果等于all则所有值都为空才删除
3)inplace:如果 为True则修改当前df,否则返回新的df
3、fillna:填充空值:
1)value:用于填充的值,可以是单个值,或者字典(key是列名,value是值)
2)method:等于ffill使用前一个不为空的值填充forword fill;等于bfill使用后一个不为空的值填充backword fill
3)axis:按行还是列填充,{0 or ‘index’ , 1 or ‘columns’}
4)inplace:如果为True则修改当前df,否则返回新的df

import pandas as pd


# 实例:特殊Excel的读取、清洗、处理
# 步骤1:读取excel的时候,忽略前几个空行
studf = pd.read_excel("./student_excel.xlsx",skiprows = 2)
print(studf)

# 步骤2:检测空值,True表示空
print(studf.isnull())

# 检测单列空值
print(studf["分数"].isnull())

# 与isnull相反的函数 notnull(),NaN(False)表示空,可以筛选出不为空的行
print(studf["分数"].notnull)

# 筛选没有空分数的所有行
print(studf.loc[studf["分数"].notnull(),:])

# 步骤3:删除掉全是空值的列,columns表示列
print(studf.dropna(axis="columns",how="all",inplace=True))
print(studf)

# 步骤4:删除掉全是空值的行,index表示行
print(studf.dropna(axis="index",how="all",inplace=True))
print(studf)

# 步骤5:将分数列为空的填充为0分
# 方法一:传入一个字典,key表示列名
studf.fillna({"分数": 0.0})
print(studf)
# 方法二:
studf.loc[:,"分数"] = studf["分数"].fillna(0.0)
print(studf)

# 步骤6:将姓名的缺失值填充
# 使用前面的有效值填充,用 ffill: forward fill
studf.loc[:,"姓名"] = studf["姓名"].fillna(method="ffill")
print(studf)

# 步骤7:将清洗好的excel保存,index=False表示不保存索引
studf.to_excel("./student_excel_clean.xlsx",index=False)

八、Pandas 的SettingWithCopyWarning 报警复现、原因、解决方案

import pandas as pd

# 0、读取数据
fpath = "beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

# 替换温度后的℃
df.loc[:,"bWendu"]= df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:,"yWendu"]= df["yWendu"].str.replace("℃","").astype("int32")

# 1、复现报警
# 只选出3月份的数据用于分析
condition = df["ymd"].str.startswith("2018-03")
# 设置温差
df[condition]["wen_cha"] = df["bWendu"] - df["yWendu"] # 报警出现
# 查看是否修改成功
df[condition].head() # 建议用这个方式:.loc[row_indexer,col_indexer] = value instead

# 2、原因
"""
    发出警告的代码:df[condition]["wen_cha"] = df["bWendu"] - df["yWendu"]
    代码相当于:df.get(condition).set(wen_cha),第一步的get发出了报警
    链式操作其实是两个步骤,先get后set,get得到的dataframe可能是view也可能是copy,pandas发出警告
    官方文档: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  核心要决:pandas的dataframe的修改写操作,只允许在源dataframe上进行,一步到位
"""
# 3、解决方法
    # 方法一:将get + set的两步操作,改成set 的一步操作
df.loc[condition,"wen_cha"] = df["bWendu"]-df["yWendu"]
print(df[condition].head())
    # 方法二:如果需要预筛选数据做后续的处理分析,使用copy复制dataframe
df_month3 = df[condition].copy()
print(df_month3)
df_month3["wen_cha"] = df["bWendu"]-df["yWendu"]
print(df_month3.head())

"""
    总之,pandas不允许先筛选子dataframe,再进行修改写入
    要么使用.loc实现一个步骤直接修改源dataframe
    要么先复制一个子dataframe再一个步骤执行修改
"""

九、Pandas数据排序

Series的排序:
Series.sort_values(ascending=True,inplace=False)
参数说明:
ascending:默认为True升序排序,为False降序排序
inplace:是否修改原始Series

DataFrame的排序:
DataFrame.sort_values(by,asendong=True,inplace=False)
参数说明:
by:字符串或才List<字符串>,单列排序 或者多列排序
ascending: bool或者List,升序还是降序,如果 是list对应by的多列
inplace: 是否修改原始DataFrame

import pandas as pd

# 0、读取数据
fpath = "beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)

# 替换温度后的℃
df.loc[:,"bWendu"]= df["bWendu"].str.replace("℃","").astype("int32")
df.loc[:,"yWendu"]= df["yWendu"].str.replace("℃","").astype("int32")

# 1、Series的排序
default=df['aqi'].sort_values() # 默认,升序
print(default)
df_false=df['aqi'].sort_values(ascending=False)  # 降序
print(df_false)
tianqi = df['tianqi'].sort_values() # 中文内容排序
print(tianqi)

# 2、DataFrame的排序
# 2.1 单列排序
default = df.sort_values(by='aqi')
print(default)
df_false= df.sort_values(by='aqi',ascending=False)
print(df_false)

# 2.2 多列排序
# 按空气质量等级、最高温度排序 ,默认升序
default = df.sort_values(by=['aqiLevel','bWendu'])
print(default)
# 两个字段都是降序
df_false=df.sort_values(by=["aqiLevel",'bWendu'],ascending=False)
print(df_false)
# 分别指定升序和降序
option_value=df.sort_values(by=['aqiLevel','bWendu'],ascending=[True,False])
print(option_value)

十、Pandaspb字符串处理

前面我们已经使用了字符串的处理函数:
df[“bWendu”].str.replace(“℃”).astype(“int32”)

Pandas的字符串处理:
1、使用方法:先获取Series的str属性,然后在属性上调用函数;
2、只能在字符串列上使用,不能在数字列上使用;
3、DATaFrame上没有属性和处理方法
4、Series.str并不Python原生字符串,而是自己的一套方法,不过大部分和原生str很相似;

Series.str字符串方法列表参考文档:
https://pandas.pydata.org/pandas-docs/stable/reference/series.html#string-handling

本节演示内容:
1、获取Series的属性,然后使用各种字符串处理函数
2、使用str的startswith、contains等bool类Series可以做条件查询
3、需要多次str处理的链式操作
4、使用正则表达式的处理

import pandas as pd
fpath = "beijing_tianqi_2018.csv"
df=pd.read_csv(fpath)
# print(df.dtypes) # 类型object,都是字符串的列

# 1、获取Series的str属性,使用各种字符串处理函数
# get_str = df["bWendu"].str # 获取df["bWendu"]的属性,发现是字符串StringMethods
# print(get_str)

# 字符串替换函数
# df = df["bWendu"].str.replace("℃","")
# print(df)

# 判断是不是数字,返回布尔值
# isNum=df["bWendu"].str.isnumeric()
# print(isNum)

# 不能在数字列上使用 .len(),只能在 string类型的列上使用
# get_len=df["aqi"].str.len()
# print(get_len)

# 2、使用str的startswith、contains等得到bool的Series可以做条件查询
# startswith,返回布尔值,在ymd列查询 包含2018-03的行
# condition = df["ymd"].str.startswith("2018-03")
# print(condition)
# print(df[condition].head()) # 查看查询的结果

# 3、多次str处理的链式操作
"""
怎样提取201803这样的数字月份?
1、先将日期2018-03-01
2、提取月份字符串20180301
"""
# 替换 “-” 为“”
# new_mon=df["ymd"].str.replace("-","")
# print(new_mon)

# 每次调用函数 ,都返回一个新Series,报错,只能在str上使用
# get_mon = df["ymd"].str.replace("-","").slice(0,6)
# print(get_mon)

# 正确做法,slice就是切片语法
# new_mon=df["ymd"].str.replace("-","").str.slice(0,6)
# print(new_mon)

# slice就是切片语法,可以直接用切片.str[0:6]
# new_mon = df["ymd"].str.replace("-","").str[0:6]
# print(new_mon)

# 4、使用正则表达式的处理
# 添加新列,axis:0表示行,1表示列
def get_ymd(x):
    year,month,day = x["ymd"].split("-")
    return f"{year}年{month}月{day}日"
df["中文日期"] = df.apply(get_ymd,axis = 1)
print(df["中文日期"])

# 怎样装饰“2018年01月05日”中的年月日去掉
# 方法一:链式replace
# new_ymd=df["中文日期"].str.replace("年","").str.replace("月","").str.replace("日","")
# print(new_ymd)

# 方法二:正则表达式替换
new_ymd=df["中文日期"].str.replace("[年月日]","")
print(new_ymd)

十一、Pandas的axis参数怎么理解?

axis=0 或 axis = index:
如果是单行操作,就指的是某一行
如果是聚合操作,指的是跨行cross rows
axis=1 或 axis = columns:
如果是单列操作,就指的是某一列
如果是聚合操作,就指的是跨列cross columns
按哪个axis,就是这个axis要动起来(类似被 for 遍历),其它的axis保持不动

import pandas as pd
import numpy as np

# 0、构造一个DataFrame
df = pd.DataFrame(
    np.arange(16).reshape(4,4), # 生成0-11的12个数字,把它们分成3行4列
    columns=['A','B','C','D']   # 指定列名
)

print(df)

# 1、单列drop,就是删除某一列
# drop_col= df.drop("A",axis=1)
# print(drop_col)

# 2、单行drop,删除某一行
# drop_row = df.drop(1,axis=0)
# print(drop_row)

# 3、按axis=0/index执行mean聚合操作
# 反直觉:输出的不是每行的结果,而是每列的结果
# df_mean = df.mean(axis=0)
# df_mean = df.mean(axis=0) # 按哪个axis,就是这个axis要动起来(类似被 for 遍历),其它的axis保持不动
# print(df_mean)      # 删除第一行,把列变成行,列名变成索引,列的首个值变成每行的首个值

# 4、 按axis=1/columns执行mean聚合操作
# 反直觉:输出的不是每行的结果,而是每列的结果
# df_mean = df.mean(axis=1)
# print(df_mean)  # 删除列名,行数不变,只保留每行的平均值,清理其他数

# 5、再次举例,加深理解

# 返回四列每行的和
def get_sum_value(x):
    return x["A"] + x["B"] + x["C"] + x["D"]

df["sum_value"] = df.apply(get_sum_value,axis=1)

print(df["sum_value"])

十二、Pandas的索引index

把数据存储于普通的columns列也能用于数据查询,那使用index有什么好?
index的用途总结:
1、更方便的数据查询
2、使用index可以获得性能提升
3、自动的数据对齐功能
4、更多强大的数据结构支持

import pandas as pd
import time

# 0、获取数据
df = pd.read_csv("ratings.csv")
# print(df.head())
# print(df.count)

# 1、使用index查询数据
# 自定义索引,drop=Fasle,让索引列还保持在columns
# df.set_index("userId",inplace=True,drop=False)
# print(df.head())
# print(df.index)

# 使用index的查询方法
# search_index=df.loc[500].head(5)
# print(search_index)

# 使用column的condition查询方法
# search_col = df.loc[df["userId"]==500].head()
# print(search_col)

# 2、使用index会提升查询性能
"""
如果index是唯一的,Pandas会使用哈希表优化,查询性能为O(1);
如果index不是唯一的,但是有序,Pandas会使用二分查找算法,查询性能为O(logN);
如果index是完全随机的,那么每次查询都要扫描全表,查询性能为O(N).
"""
# 实验1:完全随机的顺序查询
# 将数据随机打散
# from sklearn.util import shuffle
# df_shuffle = shuffle(df)
# df_shuffle.head()
#
# # 索引是否是递增的
# df_sh_index=df_shuffle.index.is_monotonic_increasing
# print(df_sh_index)
# # 索引是否有序
# df_is_unique=df_shuffle.index.is_unique
# print(df_is_unique)

# 计时,查询id==500数据性能
# %timeit df_shuffle.loc[500]   # %timeit 是ipython的函数
"""
pycharm计时方式:
start = time.clock()
end = time.clock()
times = end - start
"""
# 实验2:将index排序后的查询
# df_sorted = df_shuffle.sort_index()
# print(df_sorted.head())
# # 索引是否是递增的
# df_sh_index=df_shuffle.index.is_monotonic_increasing
# print(df_sh_index)
# # 索引是否有序
# df_is_unique=df_shuffle.index.is_unique
# print(df_is_unique)
# 计时,查询id==500数据性能
# %timeit df_shuffle.loc[500]   # %timeit 是ipython的函数

# 3、使用index能自动对齐数据,包括Series 和 DataFrame
s1 = pd.Series([1,2,3],index=list("abc"))
print(s1)

s2 = pd.Series([2,3,4],index=list("bcd"))
print(s2)

print(s1+s2)

# 4、使用index更多更强大的数据结构支持
"""
很多强大的索引数据结构
  CategoricalIndex,基于分类数据的index,提升性能
  MultiIndex,多维索引,用于groupby多维聚合后结果等
  DatetimeIndex,时间类索引,强大的日期和时间的方法支持
"""

十三、Pandas的Merge语法

Pandas怎样实现DATaFrame的Merge
Pandas的Merge,相当于Sql的Join,将不同的表按key关联到一个表
merge的语法:
pd.merge(left,right,how=’inner’,on=None,left_on=None,right_on=None,left_index=False,right_index=False,
sort=True,suffixes=(‘_x’,’_y’),copy=True,indicator=False,validate=None)
left,right:要merge的dataframe或者有name的Series
how:join类型,’left’,’right’,’outer’,’inner’
on:join的key,left和right都需要这个key
left_on:left的df或者series的key
right_on:right的df或者series的key
left_index,right_index:使用index而不是普通的column做join
suffixes:两个元素的后缀,如果列有重名,自动添加后缀,默认是(’_x’,’_y’)
官方文档地址:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html

import pandas as pd

# 1、电影数据集的join实例
# 读取数据
# df_ratings = pd.read_csv(
#     "./movielens-1m/ratings.dat",
#     sep="::",
#     engine="python",
#     names="UserID::MoveID::Rating::Timestamp".split("::")
# )
# print(df_ratings.head())

# df_users = pd.read_csv(
#     "movielens-1m/users.dat",
#     sep="::",
#     engine="python",
#     names="UserId::Gender::Age::Occupation::Zip-code".split("::")
# )
# print(df_users.head())

# df_movies = pd.read_csv(
#     "movielens-1m/movies.dat",
#     sep="::",
#     engine="python",
#     names="moveID::Title::Genres".split("::")
# )
# print(df_movies.head())

# inner,两边都有数据才会保留
# df_ratings_users = pd.merge(
#     df_ratings,df_users,left_on="UserID",right_on="UserId",how="inner"
# )
# print(df_ratings_users.head())

# df_ratings_users_moives = pd.merge(
#     df_ratings_users,df_movies,left_on="MoveID",right_on="moveID",how="inner"
# )
# print(df_ratings_users_moives.head(10))

# 2、理解merge时数量的对齐关系
"""
以下关系要正确理解:
    one-to-one:一对一关系,关联的key都是唯一的
        比如(学号,姓名)merge(学号,年龄)
        结果条数为:1*1
    one-to-many:一对多关系,左边唯一key,右边不唯一key
        比如(学号,姓名) merge(学号,[语文成绩、数学成绩、英语成绩])
        结果条数为:1*N
    many-to-many:多对多关系,左边都不是唯一的
        比如(学号,[语文成绩、数学成绩、英语成绩]) merge (学号,[蓝球、足球、乒乓球])
        结果条数为: M*N        
"""
# 2.1 one-to-one:一对一关系,关联的key都是唯一的
name = pd.DataFrame({
        'sno':[11,12,13,14],
        'name':['name_a','name_b','name_c','name_d']
    })
# print(name)

age = pd.DataFrame({
        'sno':[11,12,13,14],
        'age':['21','22','22','23']
    })
# print(age)

stud = pd.merge(name,age,on='sno')
# print(stud)

# 2.2  one-to-many:一对多关系,左边唯一key,右边不唯一key,数目以多的一边为准
grade = pd.DataFrame({
    'sno':[11,11,11,12,12,13],
    'grade':['语文88','数学90','英语89','语文99','数字87','英语77']
})
grade = pd.merge(name,grade,on='sno')
# print(grade)

# 2.3 多对多关系merge,注意结果会出现 乘法
lovely = pd.DataFrame({
    'sno':[11,11,12,12,13],
    '爱好':['篮球','羽毛球','乒乓球','篮球','足球']
})
lovely = pd.merge(grade,lovely,on='sno')
# print(lovely)

# 3、理解left join、right join、inner join、outer join的区别
left = pd.DataFrame({
    'key':['k0','k1','k2','k3'],
    'A':['A0','A1','A2','A3'],
    'B':['B0','B1','B2','B3']
})

right = pd.DataFrame({
    'key':['k0','k1','k4','k5'],
    'C':['C0','C1','C4','C5'],
    'D':['D0','D1','D4','D5']
})
# 3、1 inner join,默认,左边和右边的key都有,才会出现在结果里
inner_join= pd.merge(left,right,how='inner')
print(inner_join)

# 3.2   left join,左边的都会出现在结果里,右边的如果无法匹配则为Null
left_join = pd.merge(left,right,how="left")
print(left_join)

# 3.3 right join,右边的都会出现在结果里,左边的如果无法匹配则为Null
right_join = pd.merge(left,right,how="right")
print(right_join)

# 3.4 outer join,两边的都会出现在结果里,如果无法匹配则为Null
outer_join = pd.merge(left,right,how="outer")
print(outer_join)

# 4、如果出现非Key的字段重名怎么办
left = pd.DataFrame({
    'key':['k0','k1','k2','k3'],
    'A':['A0','A1','A2','A3'],
    'B':['B0','B1','B2','B3']
})

right1 = pd.DataFrame({
    'key':['k0','k1','k4','k5'],
    'A':['C0','C1','C4','C5'],
    'D':['D0','D1','D4','D5']
})

# 默认是 suffixes=('_x','_y')
suff1 = pd.merge(left,right1,on='key')
print(suff1)

suff = pd.merge(left,right1,on='key',suffixes= ('_left','_right'))
print(suff)

十四、Pandas实现数据的合并concat

使用场景:
批量合并相同格式的excel、给DATaFrame添加行、给DATaFrame添加列
一句话说明concat语法:
使用某种合并方式(inner/outer)
沿着某个轴向(axis=0/1)
把多个Pandas对象(DATaFrame/Series)合并成一个。
concat语法:pandas.concat(objs,axis=0,join=’outer’,ignore_index=False)
objs:一个列表,内容可以是DATaFrame或者Series,可以混合
axis:默认是0代表按行合并,如果等于1代表按列合并
join: 合并的时候索引的对齐方式,默认是outer join, 也可以是inner join
ignore_index: 是否忽略掉原来的数据索引

append语法:DATaFrame.append(other,ignore_index=False)
append只有按行合并,没有按列合并,相当于concat按行的简写形式
other: 单个dataframe、series、dict,或者列表
ignore_index: 是否忽略掉原来的数据索引
参考文档:
pandas.concat的api文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.concat.html
pandas.concat的教程:https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html
pandas.append的api文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.append.html

import pandas as pd
import warnings
warnings.filterwarnings('ignore')       # 忽略报警

# 一、使用pandas.concat合并数据
# df1 = pd.DataFrame({'A':['a0','a1','a2','a3'],
#                     'B':['b0','b1','b2','b3'],
#                     'C':['c0','c1','c2','c3'],
#                     'D':['d0','d1','d2','d3'],
#                     'E':['e0','e1','e2','e3']
#                     })
#
# df2 = pd.DataFrame({'A':['a4','a5','a6','a7'],
#                     'B':['b4','b5','b6','b7'],
#                     'C':['c4',F'c5','c6','c7'],
#                     'D':['d4','d5','d6','d7'],
#                     'F':['f4','f5','f6','f7']
#                     })

# 1、默认的concat,参数为axis=0,join=outer,ignore_index=False
# default_concat = pd.concat([df1,df2])
# print(default_concat)

# 2、使用ignore_index=True可以忽略原来的索引
# default_concat = pd.concat([df1,df2],ignore_index=True)
# print(default_concat)

# 3、使用join=inner过滤掉不匹配的列
# inner_concat = pd.concat([df1,df2],ignore_index=True,join='inner')
# print(inner_concat)

# 4、使用axsi=1相当于添加新列
# 4.1 添加一列Series
# s1 = pd.Series(list(range(4)),name='F')
# add_series = pd.concat([df1,s1],axis=1)
# print(add_series)

# 4.2 添加多列Series
# s2= df1.apply(lambda x:x['A'] + "_GG",axis=1)
# print(s2)
# s2.name = "G"
# new_df = pd.concat([df1,s1,s2],axis=1)
# print(new_df)

# 列表可以只有Series
# new_series = pd.concat([s1,s2],axis=1)
# print(new_series)

# 列表是可以混合顺序的
# new_df = pd.concat([s1,df1,s2],axis=1)
# print(new_df)

# 二、使用DATaFrame.append按行合并数据
# df1 = pd.DataFrame([[1,2],[3,4]],columns=list('AB'))
# print(df1)

# df2 = pd.DataFrame([[5,6],[7,8]],columns=list('AB'))
# print(df2)

# 1、给1个DATaFrame添加另一个DATaFrame
# df = df1.append(df2)
# print(df)
# 2、 忽略原来的索引
# df = df1.append(df2,ignore_index=True)
# print(df)

# 3、可以一行一行的给DataFrame添加数据
# 一个空的DataFrame
df = pd.DataFrame(columns=['A'])
# print(df)
# A 、低性能版
# for i in range(5):
#     # 注意:这里每次都在复制
#     df = pd.append({'A': i}, ignore_index=True)
# print(df)

# B、性能好的版本
# 第一个入参是一个列表,避免了多次复制
df = pd.concat(
    [pd.DataFrame([i],columns=['A'])for i in range(5)],
    ignore_index=True
)
print(df)

十五、Pandas批量拆分与合并excel文件

Pandas批量拆分 Excel 与合并Excel
实例演示:
1、将一个大Excel等份拆成多个Excel
2、将多个小Excel合并成一个大Excel并标记来源

import os
import pandas as pd

word_dir = "./datas"
splits_dir = f"{word_dir}/splits"

# 工作目录没有 splits_dir 目录则创建
if not os.path.exists(splits_dir):
    os.mkdir(splits_dir)

# 0、读取源Excel到Pandas
df_source = pd.read_excel(f"{word_dir}/crazyant_blog_articles_source.xlsx")
# print(df_source.head())
# print(df_source.index)    # 返回RangeIndex    RangeIndex(start=0, stop=258, step=1)
# print(df_source.shape)      # 返回行数和列数   (258, 3)

# 统计总行数
total_row_count = df_source.shape[0]
# print(total_row_count)

# 一、将一个大Excel等份拆成多个Excel
"""
1、使用 df.iloc方法,将一个大的dataframe拆分成多个小dataframe
2、将使用dataframe.to_excel保存每个小Excel
"""
# 1、计算拆分后的每个Excel的行数
# 这个大Excel,会拆分给这几个人
user_name = ["xiao_shuai","xiao_wang","xiao_ming","xiao_lei","xiao_bo","xiao_hong"]
# 每个人的任务数目
splits_size = total_row_count // len(user_name)
# 不能均分的处理
if total_row_count % len(user_name) != 0:
    splits_size += 1
# print(splits_size)
# 拆分成多个DataFrame
df_subs = []
# 遍历用户列表
for idx, user_name in enumerate(user_name):
    # .iloc的开始索引
    begin = idx * splits_size
    # .iloc的结束索引
    end = begin + splits_size
    # 实现df按照iloc拆分
    df_sub = df_source.iloc[begin:end]
    # 将每个子df存入列表
    df_subs.append((idx,user_name,df_sub))

# 将每个dataframe存入excel
for idx,user_name,df_sub in df_subs:
    file_name = f"{splits_dir}/crazyant_blog_articles_{idx}_{user_name}.xlsx"
    df_sub.to_excel(file_name,index=False)

# 二、合并多个小Excel到一个大Excel
"""
1、遍历文件夹,得到要合并的Excel文件列表
2、分别读取到dataframe,给每个df添加一列用于标记来源
3、使用pd.concat进行df批量合并
"""
# 1、遍历文件夹,得到要合并的Excel名称列表
excel_names = []
for excel_name in os.listdir(splits_dir):
    excel_names.append(excel_name)
excel_name

# 2、分别读取到dataframe
df_list = []
for excel_name in excel_names:
    # 读取每个Excel到df
    excel_path = f"{splits_dir}/{excel_name}"
    df_split = pd.read_excel(excel_path)
    # 得到username
    username = excel_name.replace("crazyant_blog_articles_","").replace(".xlsx","")[2:]
    # print(excel_name)
    # 给每个df添加1列,即用户名
    df_split["username"] = username
    df_list.append(df_split)

# 3、使用pd.concat进行合并
df_merged = pd.concat(df_list)
print(df_merged.shape)
print(df_merged.head())
print(df_merged['username'].value_counts())

# 4、将合并后的dataframe输出到excel
df_merged.to_excel(f"{word_dir}/crazyant_blog_articles_merged.xlsx",index=False)

十六、Pandas怎样实现groupby分组统计

类似:SQL
select city,max(temperature) from city_weather group by city;
groupby:先对数据分组,然后在每个分组上应用聚合函数、转换函数

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt


# 加上这句,能在jupyter notebook展示 matplot图表
# %matplotlib inline
df = pd.DataFrame({'A':['foo','bar','foo','bar','foo','bar','foo','foo'],
                   'B':['one','two','one','one','three','two','one','three'],
                   'C':np.random.randn(8),
                   'D':np.random.randn(8)})
print(df)

# 1、单个groupby,查询所有数据列的统计,求和
gro_sum = df.groupby('A').sum()
print(gro_sum)  # groupby中的A变成了数据的索引列,因为要统计sum,但B列不是数字,所以被自动忽略掉

# 2、多个列groupby,查询所有数据列的统计,平均值
gro_mean = df.groupby(['A','B']).mean()
print(gro_mean) # 我们看到:(‘A','B')成对变成了二级索引

gro_mean1 = df.groupby(['A','B'],as_index=False).mean()
print(gro_mean1)    # 加上as_index=False,不让A变成索引

# 3、同时查看多种数据统计
gro_agg = df.groupby('A').agg([np.sum,np.mean,np.std])
print(gro_agg)

# 4、查看单列的结果数据统计
# 方法一:预过滤,性能更好
gro_agg1 = df.groupby('A')['C'].agg([np.sum,np.mean,np.std])
print(gro_agg1)     # 单独筛选出 C 列

# 方法二:
gro_agg2 = df.groupby('A').agg([np.sum,np.mean,np.std])['C']
print(gro_agg2)

# 5、不同列使用不同的聚合函数
gro_agg3 = df.groupby('A').agg({'C':np.sum,'D':np.mean})
print(gro_agg3)

# 二、遍历groupby 的结果理解执行流程
# for循环可以直接遍历每个group
# 1、遍历单个列聚合的分组
for_group = df.groupby('A')
print(for_group)

for name,group in for_group:
    print(name)
    print(group)
    print()

# 可以获取单个分组的数据
for_bar = for_group.get_group('bar')
print(for_bar)

# 2、遍历多个列聚合的分组
# 可以看到,name是一个2个元素的tuple,代表不同的列
for_group1 = df.groupby(['A','B'])
for name,group in for_group1:
    print(name)
    print(group)
    print()

for_foo = for_group1.get_group(('foo','one'))
print(for_foo)

# 可以直接查询group后的某几列,生成Series或者子DataFrame
for_C = for_group1['C']
for name,group in for_C:
    print(name)
    print(group)
    print(type(group))
    print()

# 其实,所有的聚合统计,都是在DATaFrame和Series上进行的

# 三、实例分组,探索天气数据
fpath = "./beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)
print(df)
# 替换掉温度后面的“”
df.loc[:,'bWendu'] = df['bWendu'].str.replace('℃','').astype('int32')
df.loc[:,'yWendu'] = df['yWendu'].str.replace('℃','').astype('int32')
print(df.head())

# 新增一列为月份
df['month'] = df['ymd'].str[:7]
print(df.head())

data = df.groupby('month')['bWendu'].max()
print(data)
print(type(data))
# 绘图
plt.plot(data)
# plt.show()
# 查看每个月的最高温度,最低温度,平均空气质量指数
print(df.head)

group_data = df.groupby('month').agg({'bWendu':np.max,'yWendu':np.min,'aqi':np.mean})
print(group_data)
print(type(group_data))
# 绘图
plt.plot(group_data)
plt.show()

十七、Pandas的分层索引MultiIndex

为什么要学习分层索引?
分层索引:在一个轴向上拥有多个索引层级,可以表达更高维度数据的形式
可以更方便的进行数据筛选,如果有序则性能更好
groupby等操作的结果,如果是多KEY,结果是分层索引,需要会使用
一般不需要自己创建分层索引(MultiIndex有构造函数但一般不用)

演示数据:百度、阿里巴巴、爱奇艺、京东四家公司的10天股票数据
数据来自:英为财经 https://cn.investing.com

import pandas as pd


path = "datas/互联网公司股票.xlsx"
stocks = pd.read_excel(path)
# print(stocks.shape)
# print(stocks.head())

# 查看包含哪些公司
coump = stocks['公司'].unique()
# print(coump)
#
# print(stocks.index)

# 查收收盘价的平均值
# print(stocks.groupby('公司')['收盘'].mean())

# 一、Series的分层索引MultiIndex
ser = stocks.groupby(['公司','日期'])['收盘'].mean()
# print(ser)
# 多维索引中,空白的意思是:使用上面的值
# print(ser.index)

# unstack把二级索引变成列
# print(ser.unstack())

# print(ser.reset_index())

# 二、Series有多索引MultiIndex怎样筛选数据?
# print(ser)

# print(ser.loc['BIDU'])

# 多层索引,可以用元组的形式筛选
# print(ser.loc[('BIDU','2019-10-03')])
#
# print(ser.loc[:,'2019-10-02'])

# 三、DataFrame的多层索引MultiIndex
# print(stocks.head())

# 输入列表做为分层索引
stocks.set_index(['公司','日期'],inplace=True)
# print(stocks)
# print(stocks.index)

# 四、DataFrame有多层索引MultiIndex怎样筛选数据?
"""
【重要知识】 在选择数据时:
    元组(key1,key2)代表筛选多层索引,其中key1是索引第一级,key2是第二级,比如key1=JD,key2=2019-10-02
    列表[key1,key2]代表同一层的多个KEY,其中key1和key2是并列的同级索引,比如key1=JD,Key2=BIDU
"""
print(stocks.loc['BIDU'])

print(stocks.loc[('BIDU','2019-10-02'),:])

print(stocks.loc[('BIDU','2019-10-03'),'开盘'])

print(stocks.loc[['BIDU','JD'],:])

print(stocks.loc[(['BIDU','JD'],'2019-10-03'),:])

print(stocks.loc[(['BIDU','JD'],'2019-10-03'),'收盘'])

print(stocks.loc[('BIDU',['2019-10-02','2019-10-03']),'收盘'])

# slice(None)代表选这一索引的所有内容
print(stocks.loc[(slice(None),['2019-10-02','2019-10-03']),:])

stocks.reset_index()

print(stocks)

十八、Pandas的数据转换函数map,apply,applymap

数据转换函数对比: map、apply、applymap:
1、map:只用于Series,实现每个值–>值的映射;
2、apply:用于Series实现每个值的处理,用于DataFrame实现某个轴的Series的处理;
3、applymap:只能用于DataFrame,用于处理该DataFrame的每个元素。

# 1、map用于Series值的转换
# 实例:将股票代码英文转换成中文名字
# Series.map(dict)  或  Series.map(function)均可
import pandas as pd
stocks = pd.read_excel("datas/互联网公司股票.xlsx")
print(stocks.head(3))

print(stocks['公司'].unique())

# 公司股票代码到中的映射,注意这里是小写
dict_company_names={
    'bidu':'百度',
    'baba':'阿里巴巴',
    'iq':'爱奇艺',
    'jd':'京东'
}
# 方法一:将'公司'列的名转为小写,再从字典中取值,成为新列
stocks['公司中文名1'] = stocks['公司'].str.lower().map(dict_company_names)
print(stocks.head())

# 方法二:Series.map(function)
# function的参数是Series的每个元素的值
stocks['公司中文名2'] = stocks['公司'].map(lambda x:dict_company_names[x.lower()])
print(stocks.head())


# 2、apply用于Series和DataFrame的转换
'''
Series.apply(function),函数的参数是每个值
DataFrame.apply(function),函数的参数是Series
'''

# Series.apply(function)    function的参数是Serises的每个值
stocks['公司中文名3'] = stocks['公司'].apply(lambda x:dict_company_names[x.lower()])
print(stocks.head())

# DataFrame.apply(funciton) function的参数是对应轴的Series
stocks['公司中文名4'] = stocks.apply(lambda x:dict_company_names[x['公司'].lower()],axis=1)
print(stocks.head())
"""
注意这个代码:
  1、apply是在stocks这个DataFrame上调用;
  2、lambda x的x是一个Series,因为指定了axis=1所以seires的key是列名,可以用x['公司']获取
"""
# 3、applymap用于DataFrame所有值的转换
sub_df = stocks[['收盘','开盘','高','低','交易量']]
print(sub_df)

# 将数值取整数,应用于所有元素
print(sub_df.applymap(lambda x:int(x)))

# 直接修改原df的这几列
stocks.loc[:,['收盘','开盘','高','低','交易量']] = sub_df.applymap(lambda x:int(x))
print(stocks.head())

十九、Pandas怎样对每个分组应用apply函数?

知识:Pandas的GroupBy遵从split、apply、comine模式。
这里的split指的是pandas的groupby,我们自己实现apply函数,apply返回的结果由pandas进行combine得到结果

GroupBy.apply(function)
function的第一个参数是dataframe
function的返回结果,可是dataframe、series、单个值,甚至和输入dataframe完全没关系
本次实例演示:
1、怎样对数值列按分组的归一化?
2、怎样取每个分组的TOPN数据?


"""
实例一:怎样对数值列按分组的归一化?
  归一化:将不同范围的数值列进行归一化,映射到[0,1]区间:
      好处:a、更容易做数据横向对比,比如价格字段是几百到几千,增幅字段是0到100
           b、机器学习模型学的更快性能更好
  归一化的公式:
                       (X - X minimum)
    X normalized = ——————————————————————————
                    (X maximum - X minimum)
演示:用户对电影评分的归一化
  每个用户的评分不同,有的乐观派评分高,有的悲观派评分低,按用户做归一化
"""
import pandas as pd

# 0、获取数据
# ratings = pd.read_csv(
#     "datas/ratings.dat",
#     sep = "::",
#     engine="python",
#     names="UserID::MovieID::Rating::Timestamp".split("::")
#     )
# print(ratings.head())
# 1、封闭函数ratings_norm, 实现按照用户ID分组,然后对其中一列归一化
def ratings_norm(df):
    """
    @param df:每个用户分组的dataframe
    """
    # min_value = df['Rating'].min()
    # max_value = df['Rating'].max()
    # # 新增Rating_norm列,保存归一化后的结果
    # df['Rating_norm'] = df['Rating'].apply(
    #     # 归一化的公式
    #     lambda x:(x - min_value) / (max_value - min_value))
    # return df
# 对 UserID 一列归一化
# ratings = ratings.groupby("UserID").apply(ratings_norm)

# print(ratings[ratings['UserID'] == 1].head())

"""
实例2:怎样取每个分组的TOPN数据?
获取2018年每个月温度最高的2天数据
"""
fpath = "beijing_tianqi_2018.csv"
df = pd.read_csv(fpath)
print(df.head())

# 替换掉温度的后缀℃
df['bWendu'] = df['bWendu'].str.replace('℃','').astype('int32')
df['yWendu'] = df['yWendu'].str.replace('℃','').astype('int32')
print(df.head())

# 新增一列为月份
df['month'] = df['ymd'].str[:7]
print(df.head())

def getWenduTopN(df,topn):
    """
    这里的df,是每个月份分组group的df
    """
    # 降序排序后,用切片语法,[-topn:]取最后的值
    return df.sort_values(by='bWendu')[['ymd','bWendu']][-topn:]

# 获取月份中列中最高温度三天的数据
print(df.groupby("month").apply(getWenduTopN,topn=3).head())

# 我们看到,grouby的apply函数返回的dataframe,其实和原来的dataframe其实可以完全不一样

二十、Pandas的stack和pivot实现数据透视

将列式数据变成二维交叉形式,便于分析,叫做重塑或透视
1、经过统计得到多维度指标数据
2、使用unstack实现数据二维透视
3、使用pivot简化透视
4、stack、unstack、pivot的语法

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 1、经过统计得到多维度指标数据
"""
非常场景的统计场景,指定多个维度,计算聚合后的指标
实例:统计得到电影评分数据集,每个月份的每个分数被评分多少次:(月份,分数1~5,次数)
"""
# 获取数据
df = pd.read_csv(
    "datas/ratings.dat",
    header=None,
    sep="::",
    engine="python",
    names="UserID::MovieID::Rating::Timestamp".split("::")
    )
print(df.head())

# 新增列,更改时间格式
df['pdata'] = pd.to_datetime(df['Timestamp'],unit='s')
# print(df.head())
# print(df.dtypes)

# 实现数据统计
df_group = df.groupby([df['pdata'].dt.month,'Rating'])['UserID'].agg(pv=np.sum)
# print(df_group.head())
"""
对这样格式的数据,我想查看按月份,不同评分的次数趋势,是没法实现的
需要将数据变成每个评分是一列才可以实现
"""
# 2、使用unstack实现数据二维透视
# 目的:想要画图对比按照月份的不同评分的数量趋势
# df_stack = df_group.unstack()
# print(df_stack)

# df_stack.plot()
# plt.show()

# unstack和stack是互逆操作
# print(df_stack.stack().head())

# 3、使用pivot简化透视
# print(df_group.head())
df_reset = df_group.reset_index()
# print(df_reset.head())

df_pivot = df_reset.pivot("pdata","Rating","pv")
print(df_pivot.head())
df_pivot.plot()
plt.show()
# pivot方法相当于对df使用set_index创建分层索引,然后调用unstack

# 4、stack、unstack、pivot的语法
"""
stack: DataFrame.stack(level=-1,dropna=True), 将column变成index,类似把横放的书籍变成竖放
level=-1代表多层索引的最内层,可以通过==0、1、2指定多层索引的对应层

unstack: DataFrame.unstack(level=-1,fill_value=None), 将index变成column,类似把竖放的书籍变成横放

pivot: DataFrame.pivot(index=None,columns=None,values=None), 指定index、columns、values实现二维透视
"""