zoukankan      html  css  js  c++  java
  • Titantic乘客数据分析

    数据来源:Kaggle

    分析目的:对数据进行分析、处理,进而训练、预测。

    一、查看数据

    import pandas as pd

    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv') #读取csv数据文件
    pd.set_option('expand_frame_repr', False) #显示每一列数据
    print(data_train)

     

    可以看到,共有12列数据,分别表示:

    • PassengerId => 乘客ID
    • Pclass => 乘客等级(1/2/3等舱位)
    • Name => 乘客姓名
    • Sex => 性别
    • Age => 年龄
    • SibSp => 堂兄弟/妹个数
    • Parch => 父母与小孩个数
    • Ticket => 船票信息
    • Fare => 票价
    • Cabin => 客舱
    • Embarked => 登船港口

    接下来通过代码查看数据信息:

    print(data_train.info())
    输出:

     

    从上面可以看出,我们的数据中‘Age’和‘Cabin’两项数据是有缺失的。

    下面从另一个角度查看数据信息:

    print(data_train.describe())
    输出:

     通过describe()我们可以查看各项数据的数量、平均值、标准差、最大最小值等。

    二、初步分析

    在对数据有了初步的认识之后,就需要了解他们之间的关系了。

    通过matplotlib函数来对数据进行可视化处理:

    import pandas as pd
    import numpy as np
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    myfont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf') #设置图表上的字体(中文显示)
    data_train = pd.read_csv('mytrain.csv')
    fig = plt.figure()
    fig.set(alpha=0.2) # 设定图表颜色alpha参数

    plt.subplot2grid((2,3),(0,0)) # 在一张大图里分列几个小图,2行3列,当前位置(0,0)
    data_train.Survived.value_counts().plot(kind='bar')# 柱状图
    plt.title(u"获救情况 (1为获救)",fontproperties=myfont) # 标题
    plt.ylabel(u"人数",fontproperties=myfont)

    plt.subplot2grid((2,3),(0,1))
    data_train.Pclass.value_counts().plot(kind="bar")
    plt.ylabel(u"人数",fontproperties=myfont)
    plt.title(u"乘客等级分布",fontproperties=myfont)

    plt.subplot2grid((2,3),(0,2))
    plt.scatter(data_train.Survived, data_train.Age)
    plt.ylabel(u"年龄",fontproperties=myfont) # 设定纵坐标名称
    plt.grid(b=True, which='major', axis='y')
    plt.title(u"按年龄看获救分布 (1为获救)",fontproperties=myfont)


    plt.subplot2grid((2,3),(1,0), colspan=2)
    data_train.Age[data_train.Pclass == 1].plot(kind='kde')
    data_train.Age[data_train.Pclass == 2].plot(kind='kde')
    data_train.Age[data_train.Pclass == 3].plot(kind='kde')
    plt.xlabel(u"年龄",fontproperties=myfont)# plots an axis lable
    plt.ylabel(u"密度",fontproperties=myfont)
    plt.title(u"各等级的乘客年龄分布",fontproperties=myfont)
    plt.legend((u'头等舱', u'2等舱',u'3等舱'),prop=myfont,loc='best') # sets our legend for our graph.

    plt.subplot2grid((2,3),(1,2))
    data_train.Embarked.value_counts().plot(kind='bar')
    plt.title(u"各登船口岸上船人数",fontproperties=myfont)
    plt.ylabel(u"人数",fontproperties=myfont)
    plt.show()
    结果图:

    通过数据可视化之后,我们可以明显看出各项数据之间的关系:

    获救人数大概是总人数的3/8;

    三等舱的人数约为一等舱和二等舱之和;

    60岁以上的人获救很少;

    头等舱的人年龄整体较高;

    S港登船人数最多等等。

    通过初步分析之后,我们对数据可以提出一些自己的猜测,如:

    头等舱的人生存可能性更大;

    年轻人生存几率较高;

    男性比女性的生存机会大;

    票价高的人生存几率高等等。

    然后进一步对数据进行分析,并验证自己的猜测。

    首先分析各船舱等级的乘客获救情况

    import pandas as pd
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    #各等级乘客获救情况
    myFont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf')
    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    #print(data_train.info())
    fig=plt.figure()
    fig.set(alpha=0.2)
    Survived_1 = data_train.Pclass[data_train.Survived==1].value_counts()
    Survived_0 = data_train.Pclass[data_train.Survived==0].value_counts()
    df = pd.DataFrame({u'Survived':Survived_1,u'dead':Survived_0})
    df.plot(kind='bar',stacked=True)
    plt.title('不同等级乘客获救情况',fontproperties=myFont)
    plt.ylabel('等级',fontproperties=myFont)
    plt.xlabel('获救',fontproperties=myFont)
    plt.show()

    结果图:

     显然,从图中我们可以看出船舱等级越高的乘客获救的比例越高。

    接下来看看不同性别的乘客获救比例:

    import pandas as pd
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    myFont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf')
    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    #print(data_train.info())
    fig=plt.figure()
    fig.set(alpha=0.2)
    Survived_1 = data_train.Survived[data_train.Sex=='male'].value_counts()
    Survived_0 = data_train.Survived[data_train.Sex=='female'].value_counts()
    df = pd.DataFrame({u'male':Survived_1,u'female':Survived_0})
    df.plot(kind='bar',stacked=True)
    plt.title('不同等级乘客获救情况',fontproperties=myFont)
    plt.ylabel('人数',fontproperties=myFont)
    plt.xlabel('性别',fontproperties=myFont)
    plt.xticks([0,1],['死亡','生存'],rotation=0,fontproperties=myFont)
    plt.show()

    结果图:

     可以看出,女性获救比例远高于男性,与我们猜测的不同。

    然后,将不同性别、船舱等级的乘客获救情况放在一起看看:

    import pandas as pd
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    myFont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf')
    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    #print(data_train.info())
    fig=plt.figure()
    fig.set(alpha=0.2)
    plt.title(u'根据仓等级和性别的获救情况',fontproperties=myFont)

    ax1=fig.add_subplot(141)
    maleSurivedC12 = data_train.Survived[data_train.Pclass!=3][data_train.Sex=='male'].value_counts()
    maleSurivedC12.plot(kind='bar')
    plt.legend(['男性/高级仓'],loc='best',prop=myFont)
    plt.xlabel('生死',fontproperties=myFont)
    plt.ylabel('数量',fontproperties=myFont)
    #plt.ylim(0,300) # 设置y轴参数
    plt.xticks([1,0],['生','死'],fontproperties=myFont)

    ax2=fig.add_subplot(142, sharey=ax1)
    femaleSurivedC12 = data_train.Survived[data_train.Pclass!=3][data_train.Sex=='female'].value_counts()
    femaleSurivedC12.plot(kind='bar')
    #plt.ylim(0,300)
    plt.legend(['女性/高级仓'],loc='best',prop=myFont)
    plt.xticks([0,1],['生','死'],fontproperties=myFont)

    ax3=fig.add_subplot(143, sharey=ax1)
    maleSurivedC3 = data_train.Survived[data_train.Pclass==3][data_train.Sex=='male'].value_counts()
    maleSurivedC3.plot(kind='bar')
    #plt.ylim(0,300)
    plt.legend(['男性/低级仓'],loc='best',prop=myFont)
    plt.xticks([1,0],['生','死'],fontproperties=myFont)

    ax4=fig.add_subplot(144, sharey=ax1)
    #plt.ylim(0,300)
    femaleSurivedC3 = data_train.Survived[data_train.Pclass==3][data_train.Sex=='female'].value_counts()
    femaleSurivedC3.plot(kind='bar')
    print(femaleSurivedC3)
    plt.legend(['女性/低级仓'],loc='best',prop=myFont)
    plt.xticks([0,1],['生','死'],fontproperties=myFont)
    plt.show()

    这里我们将一等舱、二等舱划分为高级舱、三等舱为低级舱。
    结果图:

     从图中可以看出高级仓、女性存活比例最高,低级舱、男性存活比例最低。进一步验证了上面的分析。

    下面根据登船港口分析获救情况:

    import pandas as pd
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    myFont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf')
    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    #print(data_train.info())
    plt.figure()
    embarkSurvived1 = data_train.Embarked[data_train.Survived==1].value_counts()
    embarkSurvived0 = data_train.Embarked[data_train.Survived==0].value_counts()
    df = pd.DataFrame({'Survived':embarkSurvived1,'Dead':embarkSurvived0})
    df.plot(kind='bar')
    plt.title(u'根据登船港口的获救情况',fontproperties=myFont)
    plt.xlabel('港口',fontproperties=myFont)
    plt.ylabel('人数',fontproperties=myFont)
    plt.show()
    结果图:

     从图中可以看出只有港口C的获救比例是正的,没有规律,参考意义不大。

    下面看看堂兄妹/父母孩子(SibSp/Parch)的个数对与获救情况有没有关系:

    import pandas as pd
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    myFont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf')
    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    #print(data_train.info())
    g = data_train.groupby(['SibSp','Survived'])
    df = pd.DataFrame(g.count()['PassengerId'])
    print(df)结果图:

      没有特定规律,不具有参考意义。

    三、数据预处理

    对于有数据缺失的列我们一般采用以下几种方式:

    • 如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了
    • 如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中
    • 如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。
    • 有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。

    这里首先是对Cabin的处理,由于Cabin这一列的缺失值太多,而且具体值的意思不明了,于是将其分为有数据和没数据两种。

    试着分析Cabin数据有无与乘客生存情况的关系:

    import pandas as pd
    from matplotlib import pyplot as plt
    from matplotlib import font_manager

    myFont = font_manager.FontProperties(fname='C:/Windows/Fonts/simfang.ttf')
    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    #print(data_train.info())
    plt.figure()

    Survived_Cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()
    Survived_noCabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()
    df = pd.DataFrame({'yes':Survived_Cabin,'no':Survived_noCabin}).transpose()

    df.plot(kind='bar',stacked=True)
    plt.xlabel('Cabin有无',fontproperties=myFont)
    plt.ylabel('人数',fontproperties=myFont)
    plt.title('根据Cabin有无查看获救情况',fontproperties=myFont)
    plt.show()
    结果图:

     从图中可以看出有数据的人获救比例较高,虽然有结果,但实际对乘客是否获救可能没有影响,先不作特征考虑。(这里主要考虑到有数据的人身份地位较高,可能是在出事故的时候优先救援)

    对于年龄,我们这里用scikit-learn中的RandomForest来拟合一下缺失的年龄数据

    年龄数据拟合函数:

    def set_missing_ages(df):
    # 把已有的数值型特征取出来丢进Random Forest Regressor中
    age_df = df[['Age', 'Fare', 'Parch', 'SibSp', 'Pclass']]

    # 乘客分成已知年龄和未知年龄两部分
    known_age = age_df[age_df.Age.notnull()].values
    unknown_age = age_df[age_df.Age.isnull()].values

    # y即目标年龄
    y = known_age[:, 0]

    # X即特征属性值
    X = known_age[:, 1:]

    # fit到RandomForestRegressor之中
    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
    rfr.fit(X, y)

    # 用得到的模型进行未知年龄结果预测
    predictedAges = rfr.predict(unknown_age[:, 1::])

    # 用得到的预测结果填补原缺失数据
    df.loc[(df.Age.isnull()), 'Age'] = predictedAges

    return df, rfr
    结果图:

     可以看出,年龄数据已经完成拟合并写入。

    对于Cabin这一项,我们将有数据的值改为‘Yes’,没有数据的值改为‘No’

    def set_Cabin_type(df):
    df.loc[(df.Cabin.notnull()), 'Cabin'] = "Yes"
    df.loc[(df.Cabin.isnull()), 'Cabin'] = "No"
    return df
    结果图:

    Cabin这一项数据也已经处理完毕。以上函数通过下面代码调用处理:

    pd.set_option('display.max_rows',50)
    pd.set_option('display.max_columns',500) #设置最大显示列数
    pd.set_option('expand_frame_repr', False)
    data_train, rfr = set_missing_ages(data_train)
    data_train = set_Cabin_type(data_train)

    四、建模

    逻辑回归建模时,需要输入的特征都是数值型特征,我们通常会先对类目型的特征因子化。

    dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin')
    dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked')
    dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')
    dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')

    df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1) #将上面归一化后的数据接到原数据后面。

    df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)  # 删除指定行列,axis默认为0表示删除行,1表示删除列,inplace=True表示改变原数据,False相反
    scaler = preprocessing.StandardScaler()#均值方差归一化函数
    age_scale_param = scaler.fit(df['Age'].values.reshape(-1,1)) # reshape(-1,1)转换成1列:-1表示行自动计算,1表示1列
    df['Age_scaled'] = scaler.fit_transform(df['Age'].values.reshape(-1,1), age_scale_param)
    fare_scale_param = scaler.fit(df['Fare'].values.reshape(-1,1))
    df['Fare_scaled'] = scaler.fit_transform(df['Fare'].values.reshape(-1,1), fare_scale_param)

    结果图:

     可以看到,复杂的数据变得简单了,也符合了逻辑回归的要求。

    接下来取出我们需要的特征并建模:

    # 用正则取出我们要的属性值
    train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    train_np = train_df.as_matrix()

    # y即Survival结果
    y = train_np[:, 0]

    # X即特征属性值
    X = train_np[:, 1:]

    # fit到RandomForestRegressor之中
    clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
    clf.fit(X, y)
    print(clf)
    输出:

    最后对测试数据进行同样的处理,并通过模型预测结果:

    #测试数据预处理
    data_test = pd.read_csv('E:/pythonob/data/Titantic/mytest.csv')
    data_test.loc[(data_test.Fare.isnull()), 'Fare' ] = 0
    # 接着我们对test_data做和train_data中一致的特征变换
    # 首先用同样的RandomForestRegressor模型填上丢失的年龄
    tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
    null_age = tmp_df[data_test.Age.isnull()].as_matrix()
    # 根据特征属性X预测年龄并补上
    X = null_age[:, 1:]
    predictedAges = rfr.predict(X)
    data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges

    data_test = set_Cabin_type(data_test)
    dummies_Cabin_t = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')
    dummies_Embarked_t = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')
    dummies_Sex_t = pd.get_dummies(data_test['Sex'], prefix= 'Sex')
    dummies_Pclass_t = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')


    df_test = pd.concat([data_test, dummies_Cabin_t, dummies_Embarked_t, dummies_Sex_t, dummies_Pclass_t], axis=1)
    df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
    df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'].values.reshape(-1,1), age_scale_param)
    df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'].values.reshape(-1,1), fare_scale_param)
    print(df_test)
    test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    predictions = clf.predict(test)
    result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
    result.to_csv("E:/pythonob/data/Titantic/logistic_regression_predictions.csv", index=False)
    结果:

     

     将得到的结果上传到kaggle:

     

     初次学习,效果不是很好。

    下面是对数据进行处理、建模、预测的完整代码:

    # coding = utf-8
    from sklearn.ensemble import RandomForestRegressor
    import pandas as pd
    import numpy as np
    import sklearn.preprocessing as preprocessing
    from sklearn import linear_model


    data_train = pd.read_csv('E:/pythonob/data/Titantic/mytrain.csv')
    ### 使用 RandomForestClassifier 填补缺失的年龄属性
    def set_missing_ages(df):
    # 把已有的数值型特征取出来丢进Random Forest Regressor中
    age_df = df[['Age', 'Fare', 'Parch', 'SibSp', 'Pclass']]

    # 乘客分成已知年龄和未知年龄两部分
    known_age = age_df[age_df.Age.notnull()].values
    unknown_age = age_df[age_df.Age.isnull()].values

    # y即目标年龄
    y = known_age[:, 0]

    # X即特征属性值
    X = known_age[:, 1:]

    # fit到RandomForestRegressor之中
    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
    rfr.fit(X, y)

    # 用得到的模型进行未知年龄结果预测
    predictedAges = rfr.predict(unknown_age[:, 1::])

    # 用得到的预测结果填补原缺失数据
    df.loc[(df.Age.isnull()), 'Age'] = predictedAges

    return df, rfr


    def set_Cabin_type(df):
    df.loc[(df.Cabin.notnull()), 'Cabin'] = "Yes"
    df.loc[(df.Cabin.isnull()), 'Cabin'] = "No"
    return df


    pd.set_option('display.max_rows',50)
    pd.set_option('display.max_columns',500)
    pd.set_option('expand_frame_repr', False)
    data_train, rfr = set_missing_ages(data_train)
    data_train = set_Cabin_type(data_train)

    dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin')
    dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked')
    dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')
    dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')

    df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
    df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True) # 删除指定行列,axis默认为0表示删除行,1表示删除列,inplace=True表示改变原数据,False相反
    scaler = preprocessing.StandardScaler()#均值方差归一化函数
    age_scale_param = scaler.fit(df['Age'].values.reshape(-1,1)) # reshape(-1,1)转换成1列:-1表示行自动计算,1表示1列
    df['Age_scaled'] = scaler.fit_transform(df['Age'].values.reshape(-1,1), age_scale_param)
    fare_scale_param = scaler.fit(df['Fare'].values.reshape(-1,1))
    df['Fare_scaled'] = scaler.fit_transform(df['Fare'].values.reshape(-1,1), fare_scale_param)

    # 用正则取出我们要的属性值
    train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    train_np = train_df.as_matrix()

    # y即Survival结果
    y = train_np[:, 0]

    # X即特征属性值
    X = train_np[:, 1:]

    # fit到RandomForestRegressor之中
    clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
    clf.fit(X, y)
    print(clf)

    #测试数据预处理
    data_test = pd.read_csv('E:/pythonob/data/Titantic/mytest.csv')
    data_test.loc[(data_test.Fare.isnull()), 'Fare' ] = 0
    # 接着我们对test_data做和train_data中一致的特征变换
    # 首先用同样的RandomForestRegressor模型填上丢失的年龄
    tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
    null_age = tmp_df[data_test.Age.isnull()].as_matrix()
    # 根据特征属性X预测年龄并补上
    X = null_age[:, 1:]
    predictedAges = rfr.predict(X)
    data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges

    data_test = set_Cabin_type(data_test)
    dummies_Cabin_t = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')
    dummies_Embarked_t = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')
    dummies_Sex_t = pd.get_dummies(data_test['Sex'], prefix= 'Sex')
    dummies_Pclass_t = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')


    df_test = pd.concat([data_test, dummies_Cabin_t, dummies_Embarked_t, dummies_Sex_t, dummies_Pclass_t], axis=1)
    df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
    df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'].values.reshape(-1,1), age_scale_param)
    df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'].values.reshape(-1,1), fare_scale_param)
    print(df_test)
    test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
    predictions = clf.predict(test)
    result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
    result.to_csv("E:/pythonob/data/Titantic/logistic_regression_predictions.csv", index=False)
  • 相关阅读:
    About cookie
    关于在Servlet中的Fileter
    看完这篇正则表达式,50岁的马化腾眼睛湿润了
    Tutorial中的hello2代码
    Development descriptor
    What is the Annotation?
    剖析容器的注入技术
    LDAP & Implentation
    RESTful levels、HATEOAS
    Mysql8.0导入数据时出错
  • 原文地址:https://www.cnblogs.com/cxxBoo/p/13692238.html
Copyright © 2011-2022 走看看