• Day 80 量化投资与Python——项目案例


    数据分析项目案例

    股票分析

    小结:

    需求:

    使用tushare包获取某股票的历史行情数据。
    输出该股票所有收盘比开盘上涨3%以上的日期。
    输出该股票所有开盘比前日收盘跌幅超过2%的日期。

    # 需求四:假如我从2010年1月1日开始,每月第一个交易日买入1手股票,每年最后一个交易日卖出所有股票,到今天为止,我的收益如何?

    需求一:使用tushare包获取某股票的历史行情数据。

    # 获取行情
    df = ts.get_k_data(code="600519",start='2000-01-01')
    # 保存到本地
    df.to_csv('./maotai.csv')
    # 读取本地csv文件数据
    df = pd.read_csv('./maotai.csv')
    # 删除 Unnamed: 0 这一列,将 date 列转为时间类型,并设置为 index 列
    df.drop(labels='Unnamed: 0',axis=1,inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date',inplace=True)
    print(df.info())    # 查看整个数据集合中各个数据类型
    print(df)

    需求二:输出该股票所有收盘比开盘上涨3%以上的日期。

    # (收盘-开盘)/开盘 > 0.03  返回值为 boolean 值,将 boolean 作为行索引来使用
    
    # 在分析的过程中如果产生了boolean值则下一步马上将布尔值作为源数据的行索引
    # 如果布尔值作为df的行索引,则可以取出true对应的行数据,忽略false对应的行数据
    # print((df['close'] - df['open'])/df['open'] > 0.03)   # 获取了True对应的行数据(满足需求的行数据)
    print(df.loc[(df['close'] - df['open']) / df['open'] > 0.03].index)

    需求三:输出该股票所有开盘比前日收盘跌幅超过2%的日期。

    # (今日开盘价-昨日收盘价)/昨日收盘价 < -0.02
    # print(df['close'].shift(1))    # 使 df['close'] 列整体下移一位
    print(df.loc[(df['open'] - df['close'].shift(1)) / df['close'].shift(1) < -0.02].index)

    # 需求四:假如我从2010年1月1日开始,每月第一个交易日买入1手股票,每年最后一个交易日卖出所有股票,到今天为止,我的收益如何?

    - 分析:
      - 时间节点:2010-2020
      - 一手股票:100支股票
      - 买:
        - 一个完整的年需要买入1200支股票
      - 卖:
        - 一个完整的年需要卖出1200支股票
      -买卖股票的单价:
        - 开盘价

    # 买股票:找每个月的第一个交易日对应的行数据(捕获到开盘价)==》每月的第一行数据
    # 根据月份从原始数据中提取指定的数据
    # 每月第一个交易日对应的行数据
    new_df = df['2010-01-01':]
    mairu = new_df.resample('M').first()['open'].sum() * 100  # 数据的重新取样,取出每月的第一支股票
    maichu = new_df.resample('A').last()['open'][:-1].sum() * 1200  # 取出每年最后一个交易日的收盘价
    yu = new_df['close'][-1] * 600  # 剩余股票价值
    
    # print(new_df.resample('M').first()['open']*100)
    # print(new_df.resample('A').last()['close'][:-1] * 100)
    
    print(maichu - mairu + yu)

    双均线策略

    需求一:计算该股票历史数据的5日均线和60日均线
      - 什么是均线?
        - 对于每一个交易日,都可以计算出前N天的移动平均值,然后把这些移动平均值连起来,成为一条线,就叫做N日移动平均线。移动平均线常用线有5天、10天、30天、60天、120天和240天的指标。
        - 5天和10天的是短线操作的参照指标,称做日均线指标;
        - 30天和60天的是中期均线指标,称做季均线指标;
        - 120天和240天的是长期均线指标,称做年均线指标。
      - 均线计算方法:MA=(C1+C2+C3+...+Cn)/N C:某日收盘价 N:移动平均周期(天数)

    df = ts.get_k_data(code="600519", start='2000-01-01')
    df.to_csv('./maotai.csv')
    df = pd.read_csv('./maotai.csv')
    df.drop(labels='Unnamed: 0',axis=1,inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date',inplace=True)
    
    ma5 = df['close'].rolling(5).mean()     # 5日均线
    ma30 = df['close'].rolling(30).mean()     # 30日均线
    
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    plt.plot(ma5[20:80],'h-r', label='ma5')
    plt.plot(ma30[20:80],'h-b', label='ma30')
    plt.legend()
    plt.show()

    需求二:

    - 分析输出所有金叉日期和死叉日期
      - 股票分析技术中的金叉和死叉,可以简单解释为:
        - 分析指标中的两根线,一根为短时间内的指标线,另一根为较长时间的指标线。
        - 如果短时间的指标线方向拐头向上,并且穿过了较长时间的指标线,这种状态叫“金叉”
        - 如果短时间的指标线方向拐头向下,并且穿过了较长时间的指标线,这种状态叫“死叉”
      - 一般情况下,出现金叉后,操作趋向买入;死叉则趋向卖出。当然,金叉和死叉只是分析指标之一,要和其他很多指标配合使用,才能增加操作的准确性。

    # 分析输出所有金叉日期和死叉日期                                                     
    df = pd.read_csv('./maotai.csv')                                      
    df.drop(labels='Unnamed: 0', axis=1, inplace=True)                    
    df['date'] = pd.to_datetime(df['date'])                               
    df.set_index('date', inplace=True)                                    
                                                                          
    ma5 = df['close'].rolling(5).mean()                                   
    ma30 = df['close'].rolling(30).mean()                                 
    s5 = ma5[30:] < ma30[30:]                                             
    s30 = ma5[30:] > ma30[30:]                                            
    df = df[30:]                                                          
                                                                          
    down = s5 & s30.shift(1)                                                
    print(df.loc[down].index)       # 死叉                                     
                                                                          
    up = ~(s5 | s30.shift(1))                                           
    print(df.loc[up].index)    # 金叉  

    需求三:如果我从假如我从2010年1月1日开始,初始资金为100000元,金叉尽量买入,死叉全部卖出,则到今天为止,我的炒股收益率如何?

    df = pd.read_csv('./maotai.csv')
    df.drop(labels='Unnamed: 0', axis=1, inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date', inplace=True)
    
    ma5 = df['close'].rolling(5).mean()
    ma30 = df['close'].rolling(30).mean()
    s5 = ma5[30:] < ma30[30:]
    s30 = ma5[30:] > ma30[30:]
    df = df[30:]
    
    up = ~(s5 | s30.shift(1))  # 金叉
    down = s5 & s30.shift(1)  # 死叉
    
    up_code = Series(data=1, index=(df.loc[up].index))
    down_code = Series(data=0, index=(df.loc[down].index))
    s = up_code.append(down_code)
    s = s.sort_index()['2010-01-01']
    
    first_monry = 100000  # 本金,不变
    money = first_monry  # 可变的,买股票话的钱和卖股票收入的钱都从该变量中进行操作
    hold = 0  # 持有股票的数量(股数:100股=1手)
    
    for i in range(0, len(s)):  # i表示的s这个Series中的隐式索引
        # i = 0(死叉:卖) = 1(金叉:买)
        if s[i] == 1:  # 金叉的时间
            # 基于100000的本金尽可能多的去买入股票
            # 获取股票的单价(金叉时间对应的行数据中的开盘价)
            time = s.index[i]  # 金叉的时间
            p = df.loc[time]['open']  # 股票的单价
            hand_count = money // (p * 100)  # 使用100000最多买入多少手股票
            hold = hand_count * 100
    
            money -= (hold * p)  # 将买股票话的钱从money中减去
        else:
            # 将买入的股票卖出去
    
            # 找出卖出股票的单价
            death_time = s.index[i]
            p_death = df.loc[death_time]['open']  # 卖股票的单价
            money += (p_death * hold)  # 卖出的股票收入加入到money
            hold = 0
    
    # 如何判定最后一天为金叉还是死叉
    last_monry = hold * df['close'][-1]  # 剩余股票的价值
    
    # 总收益
    money + last_monry - first_monry
    print(money)

    人口分析项目

    - 需求:
        - 导入文件,查看原始数据
        - 将人口数据和各州简称数据进行合并
        - 将合并的数据中重复的abbreviation列进行删除
        - 查看存在缺失数据的列
        - 找到有哪些state/region使得state的值为NaN,进行去重操作
        - 为找到的这些state/region的state项补上正确的值,从而去除掉state这一列的所有NaN
        - 合并各州面积数据areas
        - 我们会发现area(sq.mi)这一列有缺失数据,找出是哪些行
        - 去除含有缺失数据的行
        - 找出2010年的全民人口数据
        - 计算各州的人口密度
        - 排序,并找出人口密度最高的州
    # 导入文件,查看原始数据
    abb = pd.read_csv(r'H:py课件2-课件2_数据分析课件datastate-abbrevs.csv') #state(州的全称)abbreviation(州的简称)
    area = pd.read_csv(r'H:py课件2-课件2_数据分析课件datastate-areas.csv') #state州的全称,area (sq. mi)州的面积
    pop = pd.read_csv(r'H:py课件2-课件2_数据分析课件datastate-population.csv')#state/region简称,ages年龄,year时间,population人口数量
    
    # 将人口数据和各州简称数据进行合并
    abb_pop = pd.merge(abb,pop,left_on='abbreviation',right_on='state/region')
    abb_pop
    
    # 将合并的数据中重复的abbreviation列进行删除
    abb_pop2.drop(labels='abbreviation',axis=1,inplace=True)
    abb_pop2
    
    # 查看存在缺失数据的列
    abb_pop2.isnull().any(axis=0)
    
    # 找到有哪些state/region使得state的值为NaN,进行去重操作
    abb_pop.loc[abb_pop['state'].isnull()]['state/region'].unique()
    
    # 为找到的这些state/region的state项补上正确的值,从而去除掉state这一列的所有NaN
    # 合并各州面积数据areas
    # 我们会发现area(sq.mi)这一列有缺失数据,找出是哪些行
    # 去除含有缺失数据的行
    # 找出2010年的全民人口数据
    # 计算各州的人口密度
    # 排序,并找出人口密度最高的州

    消费记录分析

    数据文件:消费记录数据

    第一部分

    第一部分:数据类型处理
        - 数据加载
            - 字段含义:
                - user_id:用户ID
                - order_dt:购买日期
                - order_product:购买产品的数量
                - order_amount:购买金额
        - 观察数据
            - 查看数据的数据类型
            - 数据中是否存储在缺失值
            - 将order_dt转换成时间类型
            - 查看数据的统计描述
                - 计算所有用户购买商品的平均数量
                - 计算所有用户购买商品的平均花费
            - 在源数据中添加一列表示月份:astype('datetime64[M]')

    代码实现

    import pandas as pd
    from pandas import DataFrame
    # 数据加载
    df = pd.read_csv(r'H:py高级数据分析科学计算基础包-numpyCDNOW_master.txt',header=None,sep='s+',names=['user_id','order_dt','order_product','order_amount'])
    df
    
    # 查看数据的数据类型
    df.info()
    
    # 数据中是否存储在缺失值
    # df.isnull().any()
    df.notnull().all()
    
    # 将 order_amount 转换成时间类型
    df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
    df
    
    # 查看数据的统计描述
    df.describe()
    
    # 在源数据中添加一列表示月份:astype('datetime64[M]')
    df['month'] = df['order_dt'].astype('datetime64[M]')
    df
    View Code

    第二部分

    第二部分:按月数据分析
        - 用户每月花费的总金额
            - 绘制曲线图展示
        - 所有用户每月的产品购买量
        - 所有用户每月的消费总次数
        - 统计每月的消费人数

    代码实现

    # 用户每月花费的总金额
    df.groupby(by='month')['order_amount'].sum()
    
    # 绘制曲线图展示
    import matplotlib.pyplot as plt
    plt.plot(df.groupby(by='month')['order_amount'].sum())
    df.groupby(by='month')['order_amount'].sum().plot()
    
    # 所有用户每月的产品购买量
    df.groupby(by='month')['order_product'].sum()
    
    # 所有用户每月的消费总次数
    df.groupby(by='month')['user_id'].count()
    
    # 统计每月的消费人数
    df.groupby(by='month')['user_id'].nunique()
    View Code

    第三部分

    第三部分:用户个体消费数据分析
        - 用户消费总金额和消费总次数的统计描述
        - 用户消费金额和消费产品数量的散点图
        - 各个用户消费总金额的直方分布图(消费金额在1000之内的分布)
        - 各个用户消费的总数量的直方分布图(消费商品的数量在100次之内的分布)

    代码实现

    # 用户消费总金额和消费总次数的统计描述
    df.groupby('user_id')['order_amount'].sum()
    df.groupby('user_id')['order_product'].count()
    
    # 用户消费金额和消费产品数量的散点图
    order_amount = df.groupby('user_id')['order_amount'].sum()
    order_product = df.groupby('user_id')['order_product'].sum()
    plt.scatter(order_amount,order_product)
    
    # 各个用户消费总金额的直方分布图(消费金额在1000之内的分布)
    df.groupby(by='user_id').sum().query('order_amount <= 1000')['order_amount'].hist()
    
    # 各个用户消费的总数量的直方分布图(消费商品的数量在100次之内的分布)
    df.groupby(by='user_id').sum().query('order_product <= 100')['order_product'].hist()
    View Code

    第四部分

    第四部分:用户消费行为分析
        - 用户第一次消费的月份分布,和人数统计
            - 绘制线形图
        - 用户最后一次消费的时间分布,和人数统计
            - 绘制线形图
        - 新老客户的占比
            - 消费一次为新用户
            - 消费多次为老用户
                - 分析出每一个用户的第一个消费和最后一次消费的时间
                    - agg(['func1','func2']):对分组后的结果进行指定聚合
                - 分析出新老客户的消费比例
        - 用户分层
            - 分析得出每个用户的总购买量和总消费金额and最近一次消费的时间的表格rfm
            - RFM模型设计
                - R表示客户最近一次交易时间的间隔。
                    - /np.timedelta64(1,'D'):去除days
                - F表示客户购买商品的总数量,F值越大,表示客户交易越频繁,反之则表示客户交易不够活跃。
                - M表示客户交易的金额。M值越大,表示客户价值越高,反之则表示客户价值越低。
                - 将R,F,M作用到rfm表中
            - 根据价值分层,将用户分为:
                - 重要价值客户
                - 重要保持客户
                - 重要挽留客户
                - 重要发展客户
                - 一般价值客户
                - 一般保持客户
                - 一般挽留客户
                - 一般发展客户
                    - 使用已有的分层模型即可rfm_func

    代码实现

    import pandas as pd
    from pandas import DataFrame
    
    # 读取数据
    df = pd.read_csv(r'H:py高级数据分析科学计算基础包-numpyCDNOW_master.txt',sep='s+',header=None,names=['user_id','order_dt','order_product','order_amount'])
    
    # 转为时间格式
    df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
    
    # 增加月份一列
    df['month'] = df['order_dt'].astype('datetime64[M]')
    
    # 用户第一次消费的月份分布,和人数统计,绘制线形图
    df.groupby(by='user_id')['month'].min().value_counts().plot()
    
    # 用户最后一次消费的时间分布,和人数统计,绘制线形图
    df.groupby(by='user_id')['month'].max().value_counts().plot()
    
    # 新老客户的占比
    new_old_user = df.groupby(by='user_id')['order_dt'].agg(['min','max'])
    val = (new_old_user['min'] == new_old_user['max']).value_counts()
    # 新用户占比
    val[True]/(val[True] + val[False])
    # 老用户占比
    val[False]/(val[True] + val[False])
    View Code

    构建 RFM 数据表

    # 分析得出每个用户的总购买量和总消费金额and最近一次消费的时间的表格rfm
    rfm = df.pivot_table(index='user_id',aggfunc={'order_product':'sum','order_amount':'sum','order_dt':'max'})
    rfm
    
    # R表示客户最近一次交易时间的间隔
    import numpy as np
    new_date = df['order_dt'].max() # 数据中最大时间,假设为当前时间
    rfm['R'] = -(rfm.groupby(by='user_id')['order_dt'].max()-new_date)/np.timedelta64(1,'D')
    rfm
    
    # F表示客户购买商品的总数量,F值越大,表示客户交易越频繁,反之则表示客户交易不够活跃
    # M表示客户交易的金额。M值越大,表示客户价值越高,反之则表示客户价值越低
    # 将R,F,M作用到rfm表中
    
    # 删除 order_dt 列
    rfm.drop(labels='order_dt',axis=1,inplace=True)
    
    # 对列进行重命名
    rfm.columns = ['M','F','R']
    rfm
    View Code

    用户分层

    def rfm_func(x):
        level = x.map(lambda x:'1' if x>=0 else '0')
        val = level.R + level.F + level.M
        dit = {
            '111':'重要价值客户',
            '011':'重要保持客户',
            '101':'重要挽留客户',
            '001':'重要发展客户',
            '110':'一般价值客户',
            '010':'一般保持客户',
            '100':'一般挽留客户',
            '000':'一般发展客户',
        }
        respons = dit[val]
        return respons
    rfm['level'] = rfm.apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
    rfm
    View Code

    第五部分:用户生命周期

    第五部分:用户的生命周期
        - 将用户划分为活跃用户和其他用户
            - 统计每个用户每个月的消费次数
            - 统计每个用户每个月是否消费,消费为 1 否则为 0
                - 知识点:DataFrame的apply和applymap的区别
                    - applymap:返回df
                    - 将函数做用于DataFrame中的所有元素(elements)
                    - apply:返回Series
                    - apply()将一个函数作用于DataFrame中的每个行或者列
            - 将用户按照每一个月份分成:
                - unreg:观望用户(前两月没买,第三个月才第一次买,则用户前两个月为观望用户)
                - unactive:首月购买后,后序月份没有购买则在没有购买的月份中该用户的为非活跃用户
                - new:当前月就进行首次购买的用户在当前月为新用户
                - active:连续月份购买的用户在这些月中为活跃用户
                - return:购买之后间隔n月再次购买的第一个月份为该月份的回头客

    代码实现

    import pandas as pd
    from pandas import DataFrame
    df = pd.read_csv(r'H:py高级数据分析科学计算基础包-numpyCDNOW_master.txt',sep='s+',header=None,names=['user_id','order_dt','order_product','order_amount'])
    df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
    df['month'] = df['order_dt'].astype('datetime64[M]')
    df
    
    # 统计每个用户每个月的消费次数
    month_sum = df.pivot_table(index='user_id',values='order_dt',aggfunc='count',columns='month').fillna(0)
    
    # 统计每个用户每个月是否消费,消费为 1 否则为 0
    month_sum = df.pivot_table(index='user_id',values='order_dt',aggfunc='count',columns='month').fillna(0)
    df_purchase = month_sum.applymap(lambda x:1 if x>0 else 0 )
    df_purchase
    View Code

    区分用户类别

    #将df_purchase中的原始数据0和1修改为new,unactive......,返回新的df叫做df_purchase_new
    #固定算法
    month_sum = df.pivot_table(index='user_id',values='order_dt',aggfunc='count',columns='month').fillna(0)
    one_zero = month_sum.applymap(lambda x:'1' if x>0 else '0' )
    
    def active_status(data):
        status = []#某个用户每一个月的活跃度
        for i in range(18):
            
            #若本月没有消费
            if data[i] == 0:
                if len(status) > 0:
                    if status[i-1] == 'unreg':
                        status.append('unreg')
                    else:
                        status.append('unactive')
                else:
                    status.append('unreg')
                        
            #若本月消费
            else:
                if len(status) == 0:
                    status.append('new')
                else:
                    if status[i-1] == 'unactive':
                        status.append('return')
                    elif status[i-1] == 'unreg':
                        status.append('new')
                    else:
                        status.append('active')
        return status
    
    pivoted_status = df_purchase.apply(active_status,axis=1)
    
    
    # 转为 list 格式
    pivoted_status_list = pivoted_status.values.tolist()
    
    # 生成新的数据表
    # new_start_info = DataFrame(data=start_list)
    # 生成新的数据表并更换回原 index
    new_start_info = DataFrame(data=pivoted_status_list,index=month_sum.index,columns=month_sum.columns)
    new_start_info
    View Code

    - 每月【不同活跃】用户的计数
      - purchase_status_ct = df_purchase_new.apply(lambda x : pd.value_counts(x)).fillna(0)
      - 转置进行最终结果的查看

    new_start_info_ct = new_start_info.apply(lambda x : pd.value_counts(x)).fillna(0)
    new_start_info_ct
    
    new_start_info_ct.T
  • 相关阅读:
    Linux shell中运行命令后加上字符“&”的作用(转)
    初探Nginx架构
    NeoLoad系列- 快速上手教程
    Web服务器性能压力测试工具
    Web页面性能测试工具浅析
    主流压力测试工具推荐
    Jmeter系列-webdriver代码范例
    Jmeter系列-webdriver插件
    Jmeter系列-自动生成html报告
    loadrunner如何监控windows系统的资源
  • 原文地址:https://www.cnblogs.com/ysging/p/13215072.html
Copyright © 2020-2023  润新知