Kaggle竞赛系列_SpaceshipTitanic金牌方案分析_数据处理

【文章系列】

第一章 初探Kaggle竞赛————Kaggle竞赛系列_Titanic比赛

第二章 知识补充_随机森林————数学建模系列_随机森林

第三章 知识补充_LightGBM————集成学习之Boosting方法系列_LightGBM

第四章 再战Kaggle竞赛————Kaggle竞赛系列_SpaceshipTitanic比赛

第五章 重温回顾_学习金牌方法_数据分析————Kaggle竞赛系列_SpaceshipTitanic金牌方案分析_数据分析

第六章 重温回顾_学习金牌方法_数据处理————Kaggle竞赛系列_SpaceshipTitanic金牌方案分析_数据处理

第七章 重温回顾_学习金牌方法_建模分析————Kaggle竞赛系列_SpaceshipTitanic金牌方案分析_建模分析

【前言】

Spaceship Titanic比赛,类似Titanic比赛,只是增加了更多的属性以及更大的数据量,仍是一个二分类问题。

今天要分析的是一篇大神的解决方案,看完后觉得干货满满,由衷地敬佩他们对数据分析的细致程度,对比之下只觉得之前自己的分析仅仅是表面功夫,单纯靠着模型的强大能力去完成任务。看来以后还是得不断地向各位前辈大佬学习,完善自己的解决方案!!!

image-20240127131649713

项目代码 : 🚀 Spaceship Titanic: A complete guide 🏆 | Kaggle

我的解决方案:Kaggle竞赛系列_SpaceshipTitanic比赛

【比赛简介】

Spaceship Titanic比赛是一个在Kaggle上举办的机器学习挑战,参赛者的任务是预测Spaceship Titanic在与时空异常碰撞时,哪些乘客被传送到了另一个维度。这个比赛提供了从飞船损坏的计算机系统中恢复的一组个人记录,参赛者需要使用这些数据来进行预测。

【回顾】

对数据的分析已经在上一篇博客中提到:Kaggle竞赛系列_SpaceshipTitanic金牌方案分析_数据分析

本篇博客主要对数据预处理的实际操作进行分析

【正文】

(一)缺失值处理

处理缺失值的最简单方法是对连续特征使用中位数,对分类特征使用众数。这将“足够好”地工作,但如果我们想最大限度地提高模型的准确性,那么我们需要在缺失的数据中寻找模式。要做到这一点,方法是通过观察特征的联合分布,例如,来自同一组的乘客是否往往来自同一家庭?

[!TIP]

通常将要填充的属性与先前划分的分组(eg:Group、CabinDeck、Surname……)进行联合分布查看规律

1. HomePlanet与Group
(1)联合分布

查看是否能找出HomePlanet的分布规律。

# Group与HomePlanet的联合分布
GHP_gb=data.groupby(['Group','HomePlanet'])['HomePlanet'].size().unstack().fillna(0)
GHP_gb.head()

补充:

  • .size()输出不同HomePlanet出现的频次

  • .unstack()将多级索引转换为二维表格,行是Group的唯一值,列是HomePlanet的唯一值

image-20240126184219629

计算每个group中拥有不同唯一‘HomePlanet’数量的情况。

使用 seaborn 的 countplot 函数来绘制计数图。GHP_gb 是之前计算的联合分布表,GHP_gb > 0 会创建一个布尔值矩阵,其中为 True 表示对应的 ‘Group’ 拥有至少一个 ‘HomePlanet’,为 False 表示没有。然后,sum(axis=1) 对每个 ‘Group’ 计算了拥有至少一个 ‘HomePlanet’ 的数量。

# 唯一值的计数图
sns.countplot((GHP_gb>0).sum(axis=1))
plt.title('Number of unique home planets per group')

image-20240127122717901

此图说明了同一个group的乘客都来自同一个Homeplanet,因此我们可以根据此信息来填充缺失的Homeplanet信息。

(2)缺失值填充
# 之前的缺失值
HP_bef=data['HomePlanet'].isna().sum()

# HomePlanet缺失的乘客和已知HomePlanet的乘客
GHP_index=data[data['HomePlanet'].isna()][(data[data['HomePlanet'].isna()]['Group']).isin(GHP_gb.index)].index

# 填充缺失值
data.loc[GHP_index,'HomePlanet']=data.iloc[GHP_index,:]['Group'].map(lambda x: GHP_gb.idxmax(axis=1)[x])

# 打印剩余缺失值的数目
print('#HomePlanet missing values before:',HP_bef)
print('#HomePlanet missing values after:',data['HomePlanet'].isna().sum())
  • .isin(GHP_gb.index) 是一个条件表达式,它检查 Group 列中的每个元素是否在 GHP_gb 数据框的索引中。

  • GHP_index=data[data[‘HomePlanet’].isna()][(data[data[‘HomePlanet’].isna()][‘Group’]).isin(GHP_gb.index)].index 找到数据框 data 中,那些具有缺失值的 HomePlanet 列并且它们的 Group 列的值存在于 GHP_gb 数据框的索引中的行的索引值。

  • idxmax() 方法返回包含最大值的元素的索引,如果有多个元素具有相同的最大值,则返回第一个最大值的索引。

  • data.loc[GHP_index,‘HomePlanet’]=data.iloc[GHP_index,:][‘Group’].map(lambda x: GHP_gb.idxmax(axis=1)[x]):使用 .loc 方法将缺失的 ‘HomePlanet’ 值填充为相应 ‘Group’ 的最大 ‘HomePlanet’ 值,因为’HomePlanet’只有0与非零值,因此就是填充为非零的‘HomePlanet’值。

输出为:

image-20240127124258109

初步处理了131个缺失值。


2. HomePlanet与其他属性
(1)CabinDeck

具体处理过程同上

# CabinDeck与HomePlanet的联合分布
CDHP_gb=data.groupby(['Cabin_deck','HomePlanet'])['HomePlanet'].size().unstack().fillna(0)

# 缺失值的热图分析
plt.figure(figsize=(10,4))
sns.heatmap(CDHP_gb.T, annot=True, fmt='g', cmap='coolwarm')

image-20240127124801361

发现:A、B、C或T甲板上的乘客来自木卫二。G甲板上的乘客来自地球。D、E或F甲板上的乘客来自多个星球。

# 之前的缺失值
HP_bef=data['HomePlanet'].isna().sum()

# 甲板A、B、C或 T来自木卫二
data.loc[(data['HomePlanet'].isna()) & (data['Cabin_deck'].isin(['A', 'B', 'C', 'T'])), 'HomePlanet']='Europa'

# 甲板G 来自地球
data.loc[(data['HomePlanet'].isna()) & (data['Cabin_deck']=='G'), 'HomePlanet']='Earth'

# 打印剩余缺失值的数目
print('#HomePlanet missing values before:',HP_bef)
print('#HomePlanet missing values after:',data['HomePlanet'].isna().sum())

image-20240127124837886

(2)Surname
# Surname和HomePlanet联合分布
SHP_gb=data.groupby(['Surname','HomePlanet'])['HomePlanet'].size().unstack().fillna(0)

# 唯一值的计数图
plt.figure(figsize=(10,4))
sns.countplot((SHP_gb>0).sum(axis=1))
plt.title('Number of unique planets per surname')

image-20240127124900470

# 之前的缺失值
HP_bef=data['HomePlanet'].isna().sum()

# 缺失Homeplanet的乘客和已知Homeplanet的家庭成员
SHP_index=data[data['HomePlanet'].isna()][(data[data['HomePlanet'].isna()]['Surname']).isin(SHP_gb.index)].index

# 填充缺失值
data.loc[SHP_index,'HomePlanet']=data.iloc[SHP_index,:]['Surname'].map(lambda x: SHP_gb.idxmax(axis=1)[x])

# 打印剩余缺失值的数目
print('#HomePlanet missing values before:',HP_bef)
print('#HomePlanet missing values after:',data['HomePlanet'].isna().sum())

image-20240127124923967

(3)Destination
# 只剩下10个HomePlanet缺失值
data[data['HomePlanet'].isna()][['PassengerId','HomePlanet','Destination']]

image-20240127125013566

发现剩余缺失值的目的地都是TRAPPIST-1e。

# HomePlanet和Destination的联合分布
HPD_gb=data.groupby(['HomePlanet','Destination'])['Destination'].size().unstack().fillna(0)

# 缺失值的热图
plt.figure(figsize=(10,4))
sns.heatmap(HPD_gb.T, annot=True, fmt='g', cmap='coolwarm')

image-20240127125042878

去往TRAPPIST-1e的大多来自地球,因此填充为地球。

# 之前的缺失值
HP_bef=data['HomePlanet'].isna().sum()

# 用地球(如果不在D甲板上)或火星(如果在D甲板上)填充剩余的HomePlanet缺失值,用地球(如果不在D甲板上)或火星(如果在D甲板上)填充剩余的HomePlanet缺失值
data.loc[(data['HomePlanet'].isna()) & ~(data['Cabin_deck']=='D'), 'HomePlanet']='Earth'
data.loc[(data['HomePlanet'].isna()) & (data['Cabin_deck']=='D'), 'HomePlanet']='Mars'

print('#HomePlanet missing values before:',HP_bef)
print('#HomePlanet missing values after:',data['HomePlanet'].isna().sum())

3. Surname与Group
(1)联合分布
# Group和Surname的联合分布
GSN_gb=data[data['Group_size']>1].groupby(['Group','Surname'])['Surname'].size().unstack().fillna(0)

# 唯一值的计数图
plt.figure(figsize=(10,4))
sns.countplot((GSN_gb>0).sum(axis=1))
plt.title('Number of unique surnames by group')

image-20240127125310602

同一个Group中的乘客绝大多数都来自同一个Surname(家族)

(2)缺失值填充
# 之前的缺失值
SN_bef=data['Surname'].isna().sum()

# Surname缺失的乘客,在已知大多数Surname的group中
GSN_index=data[data['Surname'].isna()][(data[data['Surname'].isna()]['Group']).isin(GSN_gb.index)].index

# 填充缺失值
data.loc[GSN_index,'Surname']=data.iloc[GSN_index,:]['Group'].map(lambda x: GSN_gb.idxmax(axis=1)[x])

print('#Surname missing values before:',SN_bef)
print('#Surname missing values after:',data['Surname'].isna().sum())

image-20240127125640970

根据作者所说,这已经是该属性缺失值所能处理的极限了,对于剩余缺失值,我们会将其丢弃。

# 用离群值替换NaN(这样我们就可以使用map)
data['Surname'].fillna('Unknown', inplace=True)

# 更新family size
data['Family_size']=data['Surname'].map(lambda x: data['Surname'].value_counts()[x])

# 把NaN放在异常值处
data.loc[data['Surname']=='Unknown','Surname']=np.nan

# 缺失的surname意味着没有family
data.loc[data['Family_size']>100,'Family_size']=0

4. CabinSide与Group
(1)联合分布
# Group和Cabin features的联合分布
GCD_gb=data[data['Group_size']>1].groupby(['Group','Cabin_deck'])['Cabin_deck'].size().unstack().fillna(0)
GCN_gb=data[data['Group_size']>1].groupby(['Group','Cabin_number'])['Cabin_number'].size().unstack().fillna(0)
GCS_gb=data[data['Group_size']>1].groupby(['Group','Cabin_side'])['Cabin_side'].size().unstack().fillna(0)

# 计数图
fig=plt.figure(figsize=(16,4))
plt.subplot(1,3,1)
sns.countplot((GCD_gb>0).sum(axis=1))
plt.title('#Unique cabin decks per group')

plt.subplot(1,3,2)
sns.countplot((GCN_gb>0).sum(axis=1))
plt.title('#Unique cabin numbers per group')

plt.subplot(1,3,3)
sns.countplot((GCS_gb>0).sum(axis=1))
plt.title('#Unique cabin sides per group')
fig.tight_layout()

image-20240127125847851

发现同一个Group中的乘客都来自同一个CabinSIde。

(2)缺失值填充
# 之前的缺失值
CS_bef=data['Cabin_side'].isna().sum()

# Cabin_side缺失的乘客和Cabin_side已知的乘客
GCS_index=data[data['Cabin_side'].isna()][(data[data['Cabin_side'].isna()]['Group']).isin(GCS_gb.index)].index

# 填充缺失值
data.loc[GCS_index,'Cabin_side']=data.iloc[GCS_index,:]['Group'].map(lambda x: GCS_gb.idxmax(axis=1)[x])

print('#Cabin_side missing values before:',CS_bef)
print('#Cabin_side missing values after:',data['Cabin_side'].isna().sum())

image-20240127130032499


5. CabinSide与其他属性
(1)Surname
# Surname和Cabin side的联合分布
SCS_gb=data[data['Group_size']>1].groupby(['Surname','Cabin_side'])['Cabin_side'].size().unstack().fillna(0)

SCS_gb['Ratio']=SCS_gb['P']/(SCS_gb['P']+SCS_gb['S'])

# 比率直方图
plt.figure(figsize=(10,4))
sns.histplot(SCS_gb['Ratio'], kde=True, binwidth=0.05)
plt.title('Ratio of cabin side by surname')

image-20240127130144780


print('Percentage of families all on the same cabin side:', 100*np.round((SCS_gb['Ratio'].isin([0,1])).sum()/len(SCS_gb),3),'%')

SCS_gb.head()

image-20240127130225495

说明同一家族的人大多数都分布在同一个CabinSIde。

# 之前的缺失值
CS_bef=data['Cabin_side'].isna().sum()

# 丢弃比率这个属性
SCS_gb.drop('Ratio', axis=1, inplace=True)

# Cabin_side缺失的乘客和已知Cabin_side的family
SCS_index=data[data['Cabin_side'].isna()][(data[data['Cabin_side'].isna()]['Surname']).isin(SCS_gb.index)].index

# 填充缺失值
data.loc[SCS_index,'Cabin_side']=data.iloc[SCS_index,:]['Surname'].map(lambda x: SCS_gb.idxmax(axis=1)[x])

# 丢弃surname这个属性(我们不再需要了)
data.drop('Surname', axis=1, inplace=True)

print('#Cabin_side missing values before:',CS_bef)
print('#Cabin_side missing values after:',data['Cabin_side'].isna().sum())

image-20240127130344274

剩下的缺失值我们用离群值替代。

data['Cabin_side'].value_counts()

image-20240127130436050

# 之前的缺失值
CS_bef=data['Cabin_side'].isna().sum()

# 用离群值填充剩余的缺失值
data.loc[data['Cabin_side'].isna(),'Cabin_side']='Z'

print('#Cabin_side missing values before:',CS_bef)
print('#Cabin_side missing values after:',data['Cabin_side'].isna().sum())

image-20240127130506401


6. CabinDeck与其他属性
(1)Group
# 之前的缺失值
CD_bef=data['Cabin_deck'].isna().sum()

# Cabin_deck缺失的乘客和Cabin_deck已知的乘客
GCD_index=data[data['Cabin_deck'].isna()][(data[data['Cabin_deck'].isna()]['Group']).isin(GCD_gb.index)].index

# 填充缺失值
data.loc[GCD_index,'Cabin_deck']=data.iloc[GCD_index,:]['Group'].map(lambda x: GCD_gb.idxmax(axis=1)[x])

print('#Cabin_deck missing values before:',CD_bef)
print('#Cabin_deck missing values after:',data['Cabin_deck'].isna().sum())

image-20240127130549482

(2)HomePlanet
# 联合分布
data.groupby(['HomePlanet','Destination','Solo','Cabin_deck'])['Cabin_deck'].size().unstack().fillna(0)

image-20240127130801468

发现:来自火星的乘客很可能在F甲板。来自木卫二的乘客(或多或少)如果独自旅行,最有可能在C甲板,否则在B甲板。来自地球的乘客(或多或少)最有可能在G甲板。

# 之前的缺失值
CD_bef=data['Cabin_deck'].isna().sum()

# 用众数填充
na_rows_CD=data.loc[data['Cabin_deck'].isna(),'Cabin_deck'].index
data.loc[data['Cabin_deck'].isna(),'Cabin_deck']=data.groupby(['HomePlanet','Destination','Solo'])['Cabin_deck'].transform(lambda x: x.fillna(pd.Series.mode(x)[0]))[na_rows_CD]

print('#Cabin_deck missing values before:',CD_bef)
print('#Cabin_deck missing values after:',data['Cabin_deck'].isna().sum())

image-20240127130859162

  • na_rows_CD=data.loc[data[‘Cabin_deck’].isna(),‘Cabin_deck’].index 这一行代码找出了数据框data中“Cabin_deck”列所有缺失值(NaN)的行索引,并将这些索引存储在变量na_rows_CD中。
  • data.loc[data[‘Cabin_deck’].isna(),‘Cabin_deck’]这行代码定位到数据框data中“Cabin_deck”列的缺失值。
  • .transform(lambda x: x.fillna(pd.Series.mode(x)[0])) transform方法对每个分组应用一个函数。这里使用的是一个lambda函数,该函数用每个组内“Cabin_deck”列的众数来填充NaN值。如果存在多个众数(即最频繁出现的值),pd.Series.mode(x)[0]确保只使用第一个众数。

7. 其他属性分析

后续作者还对CabinNumber与Cabin_Deck、VIP、Age、CryoSleep、Expenditure等属性逐一进行了缺失值处理,由于篇幅原因我这里只对VIP、Age两个属性进行分析。

(1)VIP属性

因为该属性的分布高度不平衡,因此我们直接使用众数进行填充

data['VIP'].value_counts()

image-20240127163337022

# 之前的缺失值
V_bef=data['VIP'].isna().sum()

# 填充
data.loc[data['VIP'].isna(),'VIP']=False

print('#VIP missing values before:',V_bef)
print('#VIP missing values after:',data['VIP'].isna().sum())

image-20240127163329466

(2)Age属性

年龄因许多特征而异,如HomePlanet、group_size、No_spending和Cabin_deck,因此我们将根据这些子群体的中位数来推算缺失值。

# 联合分布
data.groupby(['HomePlanet','No_spending','Solo','Cabin_deck'])['Age'].median().unstack().fillna(0)

image-20240127164134360

# 之前的缺失值
A_bef=data[exp_feats].isna().sum().sum()

# 中位数填充
na_rows_A=data.loc[data['Age'].isna(),'Age'].index
data.loc[data['Age'].isna(),'Age']=data.groupby(['HomePlanet','No_spending','Solo','Cabin_deck'])['Age'].transform(lambda x: x.fillna(x.median()))[na_rows_A]

print('#Age missing values before:',A_bef)
print('#Age missing values after:',data['Age'].isna().sum())

image-20240127164203068

更新年龄分组属性

data.loc[data['Age']<=12,'Age_group']='Age_0-12'
data.loc[(data['Age']>12) & (data['Age']<18),'Age_group']='Age_13-17'
data.loc[(data['Age']>=18) & (data['Age']<=25),'Age_group']='Age_18-25'
data.loc[(data['Age']>25) & (data['Age']<=30),'Age_group']='Age_26-30'
data.loc[(data['Age']>30) & (data['Age']<=50),'Age_group']='Age_31-50'
data.loc[data['Age']>50,'Age_group']='Age_51+'

(二)数据预处理

1. 划分训练、测试数据集
X=data[data['PassengerId'].isin(train['PassengerId'].values)].copy()
X_test=data[data['PassengerId'].isin(test['PassengerId'].values)].copy()

2. 丢弃无用属性
X.drop(['PassengerId', 'Group', 'Group_size', 'Age_group', 'Cabin_number'], axis=1, inplace=True)
X_test.drop(['PassengerId', 'Group', 'Group_size', 'Age_group', 'Cabin_number'], axis=1, inplace=True)

3. 对数变化

对数变换用于减少分布中的偏态,特别是有较大异常值的分布。它可以使算法更容易“学习”正确的关系。我们将把它应用于Expenditure特征,因为这些特征被异常值严重扭曲(大多数人都没有花费)

fig=plt.figure(figsize=(12,20))
for i, col in enumerate(['RoomService','FoodCourt','ShoppingMall','Spa','VRDeck','Expenditure']):
    plt.subplot(6,2,2*i+1)
    sns.histplot(X[col], binwidth=100)
    plt.ylim([0,200])
    plt.title(f'{col} (original)')
    
    plt.subplot(6,2,2*i+2)
    sns.histplot(np.log(1+X[col]), color='C1')
    plt.ylim([0,200])
    plt.title(f'{col} (log-transform)')
    
fig.tight_layout()
plt.show()

补充:对数变化

优点:

  • 减少数据偏斜:很多机器学习模型和统计技术都假设数据是正态分布的。对数变换可以帮助减少数据的偏斜度,使其更接近正态分布。
  • 缩小极端值影响:在数据集中,一些极端值(或称异常值)可能会对分析结果产生不成比例的影响。对数变换有助于减少这些极端值的影响,因为它压缩了数值的范围。
  • 变量稳定化:对数变换可以稳定变量的方差,当数据的方差随着其均值增加而增加时(即方差不恒定),对数变换特别有用。
  • 线性化关系:在某些情况下,变量之间的关系可能是指数型的。对数变换可以帮助线性化这些关系,使得线性模型(如线性回归)成为可能。
  • 处理比率数据:当数据是比率或者有比例性质时(例如,收入分配),对数变换可以使数据分布变得更加对称。

缺点:

  • 无法直接对包含零值/负数的数据进行操作

image-20240127174134820

for col in ['RoomService','FoodCourt','ShoppingMall','Spa','VRDeck','Expenditure']:
    X[col]=np.log(1+X[col])
    X_test[col]=np.log(1+X_test[col])

可以看到,进行对数变化后,数据的分布比之前要“好看”很多。


4. 字典编码
# 划分连续与离散字段
numerical_cols = [cname for cname in X.columns if X[cname].dtype in ['int64', 'float64']]
categorical_cols = [cname for cname in X.columns if X[cname].dtype == "object"]

# 数据标准化
numerical_transformer = Pipeline(steps=[('scaler', StandardScaler())])

# 独热编码
categorical_transformer = Pipeline(steps=[('onehot', OneHotEncoder(drop='if_binary', handle_unknown='ignore',sparse=False))])

# 对连续字段进行数据标准化,对离散字段进行独热编码
ct = ColumnTransformer(
    transformers=[
        ('num', numerical_transformer, numerical_cols),
        ('cat', categorical_transformer, categorical_cols)],
        remainder='passthrough')

X = ct.fit_transform(X)
X_test = ct.transform(X_test)

print('Training set shape:', X.shape)

5. PCA降维

对于特征的处理,一般有两种方法

  • 特征选择,在原数据集中选择最有用的特征子集。
  • 特征提取,从原始数据中构造新的特征。

对于特征选择,常见的有Warpper、FIlter以及Embedded方法

更详细的信息可以参考我的另一篇博客kaggle竞赛系列_特征筛选

而对于特征提取,有PCA、LDA、t-SNE等等方法。

该方案通过PCA降维进行特征提取

# 提取3个主成分
pca = PCA(n_components=3)
components = pca.fit_transform(X)

# 计算这3个主成分保留了总方差的多少比例
total_var = pca.explained_variance_ratio_.sum() * 100

# 数据的降维可视化(3维)
fig = px.scatter_3d(
    components, x=0, y=1, z=2, color=y, size=0.1*np.ones(len(X)), opacity = 1,
    title=f'Total Explained Variance: {total_var:.2f}%',
    labels={'0': 'PC 1', '1': 'PC 2', '2': 'PC 3'},
    width=800, height=500
)
fig.show()

image-20240127175445474

# 查看降到多少维时保留方差比例
pca = PCA().fit(X)
fig, ax = plt.subplots(figsize=(10,4))
xi = np.arange(1, 1+X.shape[1], step=1)
yi = np.cumsum(pca.explained_variance_ratio_)
plt.plot(xi, yi, marker='o', linestyle='--', color='b')

# 绘图设置
plt.ylim(0.0,1.1)
plt.xlabel('Number of Components')
plt.xticks(np.arange(1, 1+X.shape[1], step=2))
plt.ylabel('Cumulative variance (%)')
plt.title('Explained variance by each component')
plt.axhline(y=1, color='r', linestyle='-')
plt.text(0.5, 0.85, '100% cut-off threshold', color = 'red')
ax.grid(axis='x')

image-20240127175613169

可以看到,当降到25维的时候,已经可以代表数据的原始特征。


6. 划分训练、验证数据集
X_train, X_valid, y_train, y_valid = train_test_split(X,y,stratify=y,train_size=0.8,test_size=0.2,random_state=0)

(三)归纳总结

数据分析流程

思维导图 (1)

数据处理流程

思维导图 (2)


(四)写在最后

至此,我们已经完成了对数据集的分析与预处理,下一篇我们会建立模型并解决问题。

Kaggle竞赛系列_SpaceshipTitanic金牌方案分析_建模分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值