数据分析-pandas2

发布于:2024-04-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

数据分析-pandas2

接上述1

计算同环比

我们之前讲过一个统计月度销售额的例子,我们可以通过groupby方法做分组聚合,也可以通过pivot_table生成透视表,如下所示。

sale_df = pd.read_excel(r"./dates/2020年销售数据.xlsx")
sale_df['销售总额'] = sale_df['售价']  * sale_df['销售数量']
# print(sale_df.groupby('销售区域')['销售总额'].sum()['上海'])
# print(sale_df.groupby(['销售区域',sale_df['销售日期'].dt.month])['销售总额'].sum())
# print(pd.pivot_table(sale_df, index=["销售区域",sale_df['销售日期'].dt.month], values='销售总额',aggfunc='sum'))

sale_df['月份'] = sale_df['销售日期'].dt.month
sale_df['季度'] = sale_df['销售日期'].dt.quarter
sale_df['星期'] = sale_df['销售日期'].dt.weekday
result_df = sale_df.pivot_table(index='月份', values='销售总额', aggfunc='sum')
print(result_df)

在得到月度销售额之后,如果我们需要计算月环比,这里有两种方案。第一种方案是我们可以使用shift方法对数据进行移动,将上一个月的数据与本月数据对齐,然后通过(本月销售额 - 上月销售额) / 上月销售额来计算月环比,代码如下所示。

result_df['上月销售额'] =  result_df.销售总额.shift(1)
result_df['环比'] = (result_df.销售总额 - result_df.上月销售额) / result_df.上月销售额
result_df.style.format(
    formatter={'上月销售额': '{:.0f}', '环比': '{:.2%}'},
    na_rep='hulue'
)
print(result_df)

更为简单的第二种方案是直接使用pct_change方法计算变化的百分比,我们使用DataFrame对象的pct_change方法完成环比的计算。值得一提的是,pct_change方法有一个名为periods的参数,它的默认值是1,计算相邻两项数据变化的百分比,这不就是我们想要的环比吗?如果我们有很多年的数据,在计算时把这个参数的值修改为12,就可以得到相邻两年的月同比。

result_df['环比'] = result_df.pct_change()
print(result_df)
窗后计算

DataFrame对象的rolling方法允许我们将数据置于窗口中,然后用函数对窗口中的数据进行运算和处理。例如,我们获取了某只股票近期的数据,想制作5日均线和10日均线,那么就需要先设置窗口再进行运算。我们先用如下所示的代码读取2022年百度的股票数据,数据文件可以通过下面的链接来获取。 2022年股票数据.xlsx

gu_df = pd.read_excel(r"./dates/2022年股票数据.xlsx",sheet_name='BIDU', index_col="Date")
gu_df.sort_values(by='Date', inplace=True)
print(gu_df)
###
          Date     Open      High       Low   Close    Volume
0   2022-12-30   87.450   89.4100   87.4101   88.09  11926714
1   2022-12-29   87.625   89.5200   87.0600   89.13  12535405

上面的DataFrameOpenHighLowCloseVolume五个列,分别代表股票的开盘价、最高价、最低价、收盘价和成交量,接下来我们对百度的股票数据进行窗口计算。

gu_df.rolling(5).mean()

我们也可以在Series上使用rolling设置窗口并在窗口内完成运算,例如我们可以对上面的百度股票收盘价(Close列)计算5日均线和10日均线,并使用merge函数将其组装到一个DataFrame对象中并绘制出双均线图,代码如下所示。

close_ma5 = gu_df.Close.rolling(5).mean()
print(close_ma5)
close_ma10 = gu_df.Close.rolling(10).mean()
result_df = pd.merge(close_ma5, close_ma10, left_index=True, right_index=True)
result_df.rename(columns={'Close_x': 'MA5', 'Close_y': 'MA10'}, inplace=True)
result_df.plot(kind='line', figsize=(10, 6))
plt.show()

相关性判定

在统计学中,我们通常使用协方差(covariance)来衡量两个随机变量的联合变化程度。如果变量 𝑋 的较大值主要与另一个变量 𝑌 的较大值相对应,而两者较小值也相对应,那么两个变量倾向于表现出相似的行为,协方差为正。如果一个变量的较大值主要对应于另一个变量的较小值,则两个变量倾向于表现出相反的行为,协方差为负。简单的说,协方差的正负号显示着两个变量的相关性。方差是协方差的一种特殊情况,即变量与自身的协方差。

𝑐𝑜𝑣(𝑋,𝑌)=𝐸((𝑋−𝜇)(𝑌−𝜐))=𝐸(𝑋⋅𝑌)−𝜇𝜐

如果 𝑋 和 𝑌 是统计独立的,那么二者的协方差为0,这是因为在 𝑋 和 𝑌 独立的情况下:

𝐸(𝑋⋅𝑌)=𝐸(𝑋)⋅𝐸(𝑌)=𝜇𝜐

协方差的数值大小取决于变量的大小,通常是不容易解释的,但是正态形式的协方差可以显示两变量线性关系的强弱。在统计学中,皮尔逊积矩相关系数就是正态形式的协方差,它用于度量两个变量 𝑋 和 𝑌 之间的相关程度(线性相关),其值介于-11之间。

𝑐𝑜𝑣(𝑋,𝑌)𝜎𝑋𝜎𝑌

估算样本的协方差和标准差,可以得到样本皮尔逊系数,通常用希腊字母 𝜌 表示。

𝜌=∑𝑖=1𝑛(𝑋𝑖−𝑋¯)(𝑌𝑖−𝑌¯)∑𝑖=1𝑛(𝑋𝑖−𝑋¯)2∑𝑖=1𝑛(𝑌𝑖−𝑌¯)2

我们用 𝜌 值判断指标的相关性时遵循以下两个步骤。

  1. 判断指标间是正相关、负相关,还是不相关。
    • 当 $ \rho \gt 0 $,认为变量之间是正相关,也就是两者的趋势一致。
    • 当 $ \rho \lt 0 $,认为变量之间是负相关,也就是两者的趋势相反。
    • 当 $ \rho \approx 0 $,认为变量之间是不相关的,但并不代表两个指标是统计独立的。
  2. 判断指标间的相关程度。
    • 当 $ \rho $ 的绝对值在 $ [0.6,1] $ 之间,认为变量之间是强相关的。
    • 当 $ \rho $ 的绝对值在 $ [0.1,0.6) $ 之间,认为变量之间是弱相关的。
    • 当 $ \rho $ 的绝对值在 $ [0,0.1) $ 之间,认为变量之间没有相关性。

皮尔逊相关系数适用于:

  1. 两个变量之间是线性关系,都是连续数据。
  2. 两个变量的总体是正态分布,或接近正态的单峰分布。
  3. 两个变量的观测值是成对的,每对观测值之间相互独立。

这里,我们顺便说一下,如果两组变量并不是来自于正态总体的连续值,我们该如何判断相关性呢?对于定序尺度(等级),我们可以使用斯皮尔曼秩相关系数,其计算公式如下所示: r s = 1 − 6 ∑ d i 2 n ( n 2 − 1 ) r_{s}=1-{\frac {6\sum d_{i}^{2}}{n(n^{2}-1)}} rs=1n(n21)6di2 其中, d i = R ⁡ ( X i ) − R ⁡ ( Y i ) d_{i}=\operatorname {R} (X_{i})-\operatorname {R} (Y_{i}) di=R(Xi)R(Yi),即每组观测中两个变量的等级差值, n n n为观测样本数。

对于定类尺度(类别),我们可以使用卡方检验的方式来判定其是否相关。其实很多时候,连续值也可以通过分箱的方式处理成离散的等级或类别,然后使用斯皮尔曼秩相关系数或卡方检验的方式来判定相关性。

DataFrame对象的cov方法和corr方法分别用于计算协方差和相关系数,corr方法有一个名为method的参数,其默认值是pearson,表示计算皮尔逊相关系数;除此之外,还可以指定kendallspearman来计算肯德尔系数或斯皮尔曼秩相关系数。

索引扩展

我们再来看看Index类型,它为SeriesDataFrame对象提供了索引服务,有了索引我们就可以排序数据(sort_index方法)、对齐数据(在运算和合并数据时非常重要)并实现对数据的快速检索(索引运算)。由于DataFrame类型表示的是二维数据,所以它的行和列都有索引,分别是indexcolumnsIndex类型的创建的比较简单,通常给出datadtypename三个参数即可,分别表示作为索引的数据、索引的数据类型和索引的名称。由于Index本身也是一维的数据,索引它的方法和属性跟Series非常类似,你可以尝试创建一个Index对象,然后尝试一下之前学过的属性和方法在Index类型上是否生效。接下来,我们主要看看Index的几种子类型。

范围索引

范围索引是由具有单调性的整数构成的索引,我们可以通过RangeIndex构造器来创建范围索引,也可以通过RangeIndex类的类方法from_range来创建范围索引,代码如下所示。

代码:

sales_data = np.random.randint(400, 1000, 12)
index = pd.RangeIndex(1, 13, name='月份')
ser = pd.Series(data=sales_data, index=index)
ser

输出:

月份
1     703
2     705
3     557
4     943
5     961
6     615
7     788
8     985
9     921
10    951
11    874
12    609
dtype: int64
分类索引

分类索引是由定类尺度构成的索引。如果我们需要通过索引将数据分组,然后再进行聚合操作,分类索引就可以派上用场。分类索引还有一个名为reorder_categories的方法,可以给索引指定一个顺序,分组聚合的结果会按照这个指定的顺序进行呈现,代码如下所示。

代码:

sales_data = [6, 6, 7, 6, 8, 6]
index = pd.CategoricalIndex(
    data=['苹果', '香蕉', '苹果', '苹果', '桃子', '香蕉'],
    categories=['苹果', '香蕉', '桃子'],
    ordered=True
)
ser = pd.Series(data=sales_data, index=index)
ser

输出:

苹果    6
香蕉    6
苹果    7
苹果    6
桃子    8
香蕉    6
dtype: int64

基于索引分组数据,然后使用sum进行求和。

ser.groupby(level=0).sum()

输出:

苹果    19
香蕉    12
桃子     8
dtype: int64

指定索引的顺序。

ser.index = index.reorder_categories(['香蕉', '桃子', '苹果'])
ser.groupby(level=0).sum()

输出:

香蕉    12
桃子     8
苹果    19
dtype: int64
多级索引

Pandas 中的MultiIndex类型用来表示层次或多级索引。可以使用MultiIndex类的类方法from_arraysfrom_productfrom_tuples等来创建多级索引,我们给大家举几个例子。

代码:

tuples = [(1, 'red'), (1, 'blue'), (2, 'red'), (2, 'blue')]
index = pd.MultiIndex.from_tuples(tuples, names=['no', 'color'])
index

输出:

MultiIndex([(1,  'red'),
            (1, 'blue'),
            (2,  'red'),
            (2, 'blue')],
           names=['no', 'color'])

代码:

arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']]
index = pd.MultiIndex.from_arrays(arrays, names=['no', 'color'])
index

输出:

MultiIndex([(1,  'red'),
            (1, 'blue'),
            (2,  'red'),
            (2, 'blue')],
           names=['no', 'color'])

代码:

sales_data = np.random.randint(1, 100, 4)
ser = pd.Series(data=sales_data, index=index)
ser

输出:

no  color
1   red      43
    blue     31
2   red      55
    blue     75
dtype: int64

代码:

ser.groupby('no').sum()

输出:

no
1     74
2    130
dtype: int64

代码:

ser.groupby(level=1).sum()

输出:

color
blue    106
red      98
dtype: int64

代码:

stu_ids = np.arange(1001, 1006)
semisters = ['期中', '期末']
index = pd.MultiIndex.from_product((stu_ids, semisters), names=['学号', '学期'])
courses = ['语文', '数学', '英语']
scores = np.random.randint(60, 101, (10, 3))
df = pd.DataFrame(data=scores, columns=courses, index=index)
df

输出:

             语文 数学 英语
学号	学期			
1001  期中	93	77	60
      期末	93	98	84
1002  期中	64	78	71
      期末	70	71	97
1003  期中	72	88	97
      期末	99	100	63
1004  期中	80	71	61
      期末	91	62	72
1005  期中	82	95	67
      期末	84	78	86

根据第一级索引分组数据,按照期中成绩占25%,期末成绩占75% 的方式计算每个学生每门课的成绩。

代码:

df.groupby(level=0).agg(lambda x: x.values[0] * 0.25 + x.values[1] * 0.75)

输出:

        语文    数学    英语
学号			
1001	93.00	92.75	78.00
1002	68.50	72.75	90.50
1003	92.25	97.00	71.50
1004	88.25	64.25	69.25
1005	83.50	82.25	81.25
间隔索引

间隔索引顾名思义是使用固定的间隔范围充当索引,我们通常会使用interval_range函数来创建间隔索引,代码如下所示。

代码:

index = pd.interval_range(start=0, end=5)
index

输出:

IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]], dtype='interval[int64, right]')

IntervalIndex有一个名为contains的方法,可以检查范围内是否包含了某个元素,如下所示。

代码:

index.contains(1.5)

输出:

array([False,  True, False, False, False])

IntervalIndex还有一个名为overlaps的方法,可以检查一个范围跟其他的范围是否有重叠,如下所示。

代码:

index.overlaps(pd.Interval(1.5, 3.5))

输出:

array([False,  True,  True,  True, False])

如果希望间隔范围是左闭右开的状态,可以在创建间隔索引时通过closed='left'来做到;如果希望两边都是关闭状态,可以将close参数的值赋值为both,代码如下所示。

代码:

index = pd.interval_range(start=0, end=5, closed='left')
index

输出:

IntervalIndex([[0, 1), [1, 2), [2, 3), [3, 4), [4, 5)], dtype='interval[int64, left]')

代码:

index = pd.interval_range(start=pd.Timestamp('2022-01-01'), end=pd.Timestamp('2022-01-04'), closed='both')
index

输出:

IntervalIndex([[2022-01-01, 2022-01-02], [2022-01-02, 2022-01-03], [2022-01-03, 2022-01-04]], dtype='interval[datetime64[ns], both]')
日期时间索引

DatetimeIndex应该是众多索引中最复杂最重要的一种索引,我们通常会使用date_range()函数来创建日期时间索引,该函数有几个非常重要的参数startendperiodsfreqtz,分别代表起始日期时间、结束日期时间、生成周期、采样频率和时区。我们先来看看如何创建DatetimeIndex对象,再来讨论它的相关运算和操作,代码如下所示。

代码:

pd.date_range('2021-1-1', '2021-6-30', periods=10)

输出:

DatetimeIndex(['2021-01-01', '2021-01-21', '2021-02-10', '2021-03-02',
               '2021-03-22', '2021-04-11', '2021-05-01', '2021-05-21',
               '2021-06-10', '2021-06-30'],
              dtype='datetime64[ns]', freq=None)

代码:

pd.date_range('2021-1-1', '2021-6-30', freq='W')

说明freq=W表示采样周期为一周,它会默认星期日是一周的开始;如果你希望星期一表示一周的开始,你可以将其修改为freq=W-MON;你也可以试着将该参数的值修改为12HMQ等,看看会发生什么,相信你不难猜到它们的含义。

输出:

DatetimeIndex(['2021-01-03', '2021-01-10', '2021-01-17', '2021-01-24',
               '2021-01-31', '2021-02-07', '2021-02-14', '2021-02-21',
               '2021-02-28', '2021-03-07', '2021-03-14', '2021-03-21',
               '2021-03-28', '2021-04-04', '2021-04-11', '2021-04-18',
               '2021-04-25', '2021-05-02', '2021-05-09', '2021-05-16',
               '2021-05-23', '2021-05-30', '2021-06-06', '2021-06-13',
               '2021-06-20', '2021-06-27'],
              dtype='datetime64[ns]', freq='W-SUN')

DatatimeIndex可以跟DateOffset类型进行运算,这一点很好理解,以为我们可以设置一个时间差让时间向前或向后偏移,具体的操作如下所示。

代码:

index = pd.date_range('2021-1-1', '2021-6-30', freq='W')
index - pd.DateOffset(days=2)

输出:

DatetimeIndex(['2021-01-01', '2021-01-08', '2021-01-15', '2021-01-22',
               '2021-01-29', '2021-02-05', '2021-02-12', '2021-02-19',
               '2021-02-26', '2021-03-05', '2021-03-12', '2021-03-19',
               '2021-03-26', '2021-04-02', '2021-04-09', '2021-04-16',
               '2021-04-23', '2021-04-30', '2021-05-07', '2021-05-14',
               '2021-05-21', '2021-05-28', '2021-06-04', '2021-06-11',
               '2021-06-18', '2021-06-25'],
              dtype='datetime64[ns]', freq=None)

代码:

index + pd.DateOffset(hours=2, minutes=10)

输出:

DatetimeIndex(['2021-01-03 02:10:00', '2021-01-10 02:10:00',
               '2021-01-17 02:10:00', '2021-01-24 02:10:00',
               '2021-01-31 02:10:00', '2021-02-07 02:10:00',
               '2021-02-14 02:10:00', '2021-02-21 02:10:00',
               '2021-02-28 02:10:00', '2021-03-07 02:10:00',
               '2021-03-14 02:10:00', '2021-03-21 02:10:00',
               '2021-03-28 02:10:00', '2021-04-04 02:10:00',
               '2021-04-11 02:10:00', '2021-04-18 02:10:00',
               '2021-04-25 02:10:00', '2021-05-02 02:10:00',
               '2021-05-09 02:10:00', '2021-05-16 02:10:00',
               '2021-05-23 02:10:00', '2021-05-30 02:10:00',
               '2021-06-06 02:10:00', '2021-06-13 02:10:00',
               '2021-06-20 02:10:00', '2021-06-27 02:10:00'],
              dtype='datetime64[ns]', freq=None)

如果Series对象或DataFrame对象使用了DatetimeIndex类型的索引,此时我们可以通过asfreq()方法指定一个时间频率来实现对数据的抽样,我们仍然以之前讲过的百度股票数据为例,给大家做一个演示。