48Kaggle 数据分析项目入门实战--房屋信贷违约风险评估预测

房屋信贷违约风险评估预测

贷款信用评估介绍

本次实验的内容主要来自于在 Kaggle 上关于贷款信用分析的一个竞赛:贷款偿还能力评估 。该竞赛是一个含奖竞赛,是由 Home Credit 公司发起,并提供相关的数据。我们来简单看一下相关的信息。
Home Credit 是一家借贷公司,其主要是为那些无银行账户客户或信用记录的客户服务。但为了确保贷款的安全性,需要对这些客户的信用或者还款能力进行评估。因此 Home Credit 利用各种数据,例如:电话缴费记录,网购交易信息等来预测其客户的还款能力。例如:电话缴费记录、网购交易信息等
而本次实验的任务则是使用 Kaggle 提供的数据,构建一个模型。使其能够预测一个顾客的信用或贷款偿还能力。

数据导入并预览

由于原始的数据比较大,考虑到运行时间的问题。这里只取了一部份数据来进行分析。如果你想分析所有的数据,可以去 Kaggle 下载完整的数据。接下来直接导入数据,并预览前 5 行。

import pandas as pd
df = pd.read_csv(
    "https://labfile.oss.aliyuncs.com/courses/1363/HomeCredit.csv")
df.head()

从上面的结果可以看到,该数据集中总共含有 122 列。查看一下数据描述。

df.describe()

查看一下数据的形状。

df.shape

由上面的输出结果可知,该数据集总共包含 5000 份数据。
查看一下数据集中都包含哪些列。

df.columns

由于列数较多,这里只选几个重要的列来讲解。如下:
AMT_CREDIT:贷款金额
AMT_INCOME_TOTAL:申请人的收入
AMT_GOODS_PRICE:如果贷款是一个商品的话,商品的价格
NAME_TYPE_SUITE:陪同申请者来申请的人
TARGET:申请者是否有能力偿还
NAME_CONTRACT_TYPE:贷款类型
NAME_INCOME_TYPE:申请者的收入来源情况
NAME_FAMILY_STATUS:申请者的婚姻状况
OCCUPATION_TYPE:申请者的职业类型
NAME_EDUCATION_TYPE:申请者的受教育情况
NAME_HOUSING_TYPE:申请者的住房情况
DAYS_BIRTH:申请人出生到申请当天的总日子数
DAYS_EMPLOYED:该特征列表示申请人的工作年限

数据可视化分析

现在先来对数据进行可视化,我们来看一下在数据集中申请贷款金额的分布情况。

import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
%matplotlib inline

plt.figure(figsize=(12, 5))
plt.title("Distribution of AMT_CREDIT")
ax = sns.distplot(df["AMT_CREDIT"])  # 画出数据分布图

从上图可以看出,大部分申请人的申请贷款金额为 0 到 2000000 区间。
来看一下申请人的收入情况。

plt.figure(figsize=(12, 5))
plt.title("Distribution of AMT_INCOME_TOTAL")
# 画出数据分布图
ax = sns.distplot(df["AMT_INCOME_TOTAL"].dropna())

从上图可以看到,大部分申请人的收入都为 0 。现在看一下,如果贷款的对象是货物的话,看一下这些货物的价格分布。

plt.figure(figsize=(12, 5))
plt.title("Distribution of AMT_GOODS_PRICE")
ax = sns.distplot(df["AMT_GOODS_PRICE"].dropna())

上面主要查看几个常见参数的数据分布。现在我们使用 ploty 绘图库来进行更具体的分析。先导入绘图工具库。

import plotly.offline as offline
import plotly.graph_objs as go
import plotly.offline as py
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=True)
offline.init_notebook_mode()

查看一下,这些申请贷款的人来贷款时,都有哪些人陪同。

temp = df["NAME_TYPE_SUITE"].value_counts()
# 画出柱状图
trace = [go.Bar(x=temp.index, y=(temp / temp.sum())*100,)]
# 设置图的字体颜色等
layout = go.Layout(
    title="Who accompanied client when applying for the  application in % ",
    xaxis=dict(title='Name of type of the Suite',
               tickfont=dict(size=14, color='rgb(107, 107, 107)')),
    yaxis=dict(title='Count of Name of type of the Suite in %',
               titlefont=dict(size=16, color='rgb(107, 107, 107)'),
               tickfont=dict(size=14, color='rgb(107, 107, 107)'))
)

fig = go.Figure(data=trace, layout=layout)
iplot(fig, filename='schoolStateNames')

从上图的显示结果可以看到,几乎 80% 的人都没有人陪同。而只有少部分人有家人或合伙人陪同。现在查看一下,申请人的还款能力。

temp = df["TARGET"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp.index, values=temp.values)]
# 设置图题
layout = go.Layout(
    title='Loan Repayed or not',
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

在上图中,1 表示有偿还能力,0 表示没有偿还能力。从上图可以看到,有超过 90% 的人没有还款能力。现在查看一下贷款的类型。

temp = df["NAME_CONTRACT_TYPE"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp.index, values=temp.values, hole=0.6)]
# 设置图题
layout = go.Layout(
    title='Types of loan',
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

在上图中,Revolving loan 表示周期性贷款,类似于分期贷款。 Cash loans 贷款表示现金贷款。由上图可知,有超过 90% 的人申请的贷款为现金贷款。接下来我们看这些申请人贷款的目的是什么。

temp1 = df["FLAG_OWN_CAR"].value_counts()
temp2 = df["FLAG_OWN_REALTY"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp1.index, values=temp1.values, domain={"x": [0, .48]}, hole=0.6),
         go.Pie(labels=temp2.index, values=temp2.values, domain={"x": [0.5, 1]}, hole=0.6)]
# 设置图中的字体,图题等
layout = go.Layout(
    title='Purpose of loan',
    annotations=[{"font": {
        "size": 20},
        "showarrow": False,
        "text": "Own Car",
        "x": 0.15,
        "y": 0.5},
        {"font": {
            "size": 20},
         "showarrow": False,
         "text": "Own Realty",
         "x": 0.85,
         "y": 0.5}])
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

从上图可知,有接近 34% 的人贷款的钱要花在车上, 30% 的人要花在物业上。接下来看一下这些申请人的收入来源。

temp = df["NAME_INCOME_TYPE"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp.index, values=temp.values, hole=0.4)]
# 设置图题
layout = go.Layout(
    title='Income sources of Applicant',
)
# 画出图题
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

从上图可知,52.1% 的人收入来源于工作,有 23.5% 的人收入来源于商业合作,有 18% 的申请者的收入主要来自于养老金。现在看一下这些申请人的婚姻状况。

temp = df["NAME_FAMILY_STATUS"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp.index, values=temp.values)]
# 设置图题
layout = go.Layout(
    title='Family Status of Applicant',
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

从上图可以看到,有 63.7% 的申请都是已婚的,有 14.7% 为单身或未婚。现在看一下这些申请者的职业。

temp = df["OCCUPATION_TYPE"].value_counts()
# 画出柱状图
trace = [go.Bar(x=temp.index, y=temp.values)]
# 设置图题
layout = go.Layout(
    title='Occupation of Applicant',
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

从上图可以看到,人数最多的职业为工人,其次是销售员等。现在来看一下申请人的受教育情况。

temp = df["NAME_EDUCATION_TYPE"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp.index, values=temp.values, hole=0.5)]
# 设置图题
layout = go.Layout(
    title='Education of Applicant',
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

有 71.5% 的人为中等学历,24% 的人为高等学历。接下来来看这些申请人的房子类型。

temp = df["NAME_HOUSING_TYPE"].value_counts()
# 画出饼状图
trace = [go.Pie(labels=temp.index, values=temp.values)]
# 设置图题
layout = go.Layout(
    title='Loan Repayed or not',
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

从上图可知,有 88.7% 的申请者有自己的房子或住在公寓,有 4.54% 的人跟父母一起住。
上面主要是通过可视化来观察数据集中一些基本的信息。现在来分析一下信息与是否有能力偿还贷款的关系。

import numpy as np
temp = df["NAME_INCOME_TYPE"].value_counts()

temp_y0 = []  # 没有偿还能力
temp_y1 = []  # 有偿还能力
for val in temp.index:
    temp_y1.append(np.sum(df["TARGET"][df["NAME_INCOME_TYPE"] == val] == 1))
    temp_y0.append(np.sum(df["TARGET"][df["NAME_INCOME_TYPE"] == val] == 0))
temp_y1 = np.array(temp_y1)
temp_y0 = np.array(temp_y0)
# 画出柱状图
trace = [go.Bar(x=temp.index, y=(temp_y1 / temp.sum()) * 100, name='YES'),
         go.Bar(x=temp.index, y=(temp_y0 / temp.sum()) * 100, name='NO'),
         go.Bar(x=temp.index, y=(temp_y1 / (temp_y0+temp_y1)) * 100, name='RATE'),
         ]
# 设置图题,字体等
layout = go.Layout(
    title="Income sources of Applicant's in terms of loan is repayed or not  in %",
    xaxis=dict(title='Income source', tickfont=dict(
        size=14, color='rgb(107, 107, 107)')),
    yaxis=dict(title='Count in %', titlefont=dict(size=16, color='rgb(107, 107, 107)'),
               tickfont=dict(size=14, color='rgb(107, 107, 107)'))
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

在上图中,YES 表示有偿还能力,NO 表示无偿还能力,RATE 表示在该取值中有偿还能力所占的比例,例如,在 Working 中,RATE 的取值越高表示当一个人的收入来源于 Working 时,该人有很大的可能有偿还能力。
从上图可知,无业人员和产假人员的偿还能力反而是最高的。当然,这两个取值的统计数量都较少,不足以说明什么问题。
接下来来看一下婚姻状况与是否有偿还能力的关系。

temp = df["NAME_FAMILY_STATUS"].value_counts()
temp_y0 = []  # 没有偿还能力
temp_y1 = []  # 有偿还能力
for val in temp.index:
    temp_y1.append(np.sum(df["TARGET"][df["NAME_FAMILY_STATUS"] == val] == 1))
    temp_y0.append(np.sum(df["TARGET"][df["NAME_FAMILY_STATUS"] == val] == 0))
temp_y1 = np.array(temp_y1)
temp_y0 = np.array(temp_y0)
# 画出柱状图
trace = [go.Bar(x=temp.index, y=(temp_y1 / temp.sum()) * 100, name='YES'),
         go.Bar(x=temp.index, y=(temp_y0 / temp.sum()) * 100, name='NO'),
         go.Bar(x=temp.index, y=(temp_y1 / (temp_y0+temp_y1)) * 100, name='RATE')]
# 设置字体、图题等
layout = go.Layout(
    title="Family Status of Applicant's in terms of loan is repayed or not in %",
    xaxis=dict(title='Family Status', tickfont=dict(
        size=14, color='rgb(107, 107, 107)')),
    yaxis=dict(title='Count in %', titlefont=dict(size=16, color='rgb(107, 107, 107)'),
               tickfont=dict(size=14, color='rgb(107, 107, 107)')))
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

从上图可知,偿还能力似乎与婚姻状况无关。现在来看申请者职业与偿还能力的关系。

temp = df["OCCUPATION_TYPE"].value_counts()

temp_y0 = []  # 没有偿还能力
temp_y1 = []  # 有偿还能力
for val in temp.index:
    temp_y1.append(np.sum(df["TARGET"][df["OCCUPATION_TYPE"] == val] == 1))
    temp_y0.append(np.sum(df["TARGET"][df["OCCUPATION_TYPE"] == val] == 0))
temp_y1 = np.array(temp_y1)
temp_y0 = np.array(temp_y0)
# 画出柱状图
trace = [go.Bar(x=temp.index, y=(temp_y1 / temp.sum()) * 100, name='YES'),
         go.Bar(x=temp.index, y=(temp_y0 / temp.sum()) * 100, name='NO'),
         go.Bar(x=temp.index, y=(temp_y1 / (temp_y0+temp_y1)) * 100, name='RATE'),
         ]
# 设置图题、字体等
layout = go.Layout(
    title="Occupation of Applicant's in terms of loan is repayed or not in %",
    width=1000,
    xaxis=dict(title='Occupation of Applicant\'s',
               tickfont=dict(size=14, color='rgb(107, 107, 107)')),
    yaxis=dict(title='Count in %', titlefont=dict(size=16, color='rgb(107, 107, 107)'),
               tickfont=dict(size=14, color='rgb(107, 107, 107)'))
)
# 显示图形
fig = go.Figure(data=trace, layout=layout)
iplot(fig)

由上图可知,像管理员、核心员工等这些职业的偿还能力都较低,而像工人、驾驶司机等职业要高一点。
当然我们还可以继续分析其他信息与偿还能力的关系,不过方法都是大同小异,因此这里不再列出。下面我们来构建预测模型。

构建预测模型

因为数据集比较复杂,而且涉及到的金融术语比较多,这里不再对数据集进行精细的预处理和特征提取工作。此外,为了方便,我们之间删除掉存在缺失值的特征列。

df_drop = df.dropna(axis=1)
df_drop.head()

编码特征
因为数据中存在一列是字符串形式的,现在将其编码成为数值形式。

from sklearn import preprocessing
# 取出非数值的列
categorical_feats = [
    f for f in df_drop.columns if df_drop[f].dtype == 'object'
]
# 对非数值的列进行编码
for col in categorical_feats:
    lb = preprocessing.LabelEncoder()
    lb.fit(list(df_drop[col].values.astype('str')))
    df_drop[col] = lb.transform(list(df_drop[col].values.astype('str')))

查看编码结果。

df_drop.head()

划分数据
在上面显示的数据中,SK_ID_CURR 列为顾客的 ID ,因此要将此列删除掉。

df_drop1 = df_drop.drop("SK_ID_CURR", axis=1)

提取训练特征数据和目标值。这里的目标值就是申请者的偿还能力,在数据集中为 TARGET 列。

data_X = df_drop1.drop("TARGET", axis=1)
data_y = df_drop1['TARGET']

为了测试预测模型的性能,划分数据集为训练数据集和测试数据集。因为数据集较大,所以只取了 20% 的数据来作为训练集。

from sklearn import model_selection

train_x, test_x, train_y, test_y = model_selection.train_test_split(data_X.values,
                                                                    data_y.values,
                                                                    test_size=0.8,
                                                                    random_state=0)

构建预测模型
当我们完成上面所有的操作之后,得到的是一份训练集和一份测试集。现在就可以构建模型了。由于这里是一个分类任务,所以我们选用随机森林来完成。

from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()  # 构建模型
model.fit(train_x, train_y)  # 训练模型

上面完成了模型的构建和训练,现在测试一下模型的准确率。

from sklearn import metrics
y_pred = model.predict(test_x)  # 预测测试集
metrics.accuracy_score(y_pred, test_y)  # 评价预测结果

我们可以使用 sklaern 提供的分类报告方法来得到一个全面的评估。

print(metrics.classification_report(y_pred, test_y))

我们还可以通过模型的结果来得到特征的重要性。

features = data_X.columns.values  # 取出数据集中的列名,即特征名
# 得到特征与其重要性
x, y = (list(x) for x in zip(*sorted(zip(model.feature_importances_, features),
                                     reverse=False)))
# 画出柱状图
trace2 = go.Bar(x=x, y=y, marker=dict(color=x, colorscale='Viridis', reversescale=True),
                name='Random Forest Feature importance', orientation='h',)
# 设置图题、字体等
layout = dict(title='Barplot of Feature importances', width=900, height=2000,
              yaxis=dict(showgrid=False, showline=False, showticklabels=True,), margin=dict(l=300,))
# 显示图形
fig1 = go.Figure(data=[trace2])
fig1['layout'].update(layout)
iplot(fig1, filename='plots')

从上面的结果可以看到,不同的特征具有不同的重要性。
上面我们主要使用逻辑回归来构建预测模型,当然还有许多中方法,现在尝试一下其他方法。

from sklearn.tree import DecisionTreeClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression

# 构建 7 种算法
models = [LogisticRegression(solver='lbfgs'),       # 逻辑回归
          RandomForestClassifier(n_estimators=100),  # 随机森林
          DecisionTreeClassifier(),                 # 决策树
          MLPClassifier(max_iter=100),              # 多层感知机
          AdaBoostClassifier(),                     # 自适应梯度提升
          BaggingClassifier(),                      # 装袋算法
          GradientBoostingClassifier()]             # 梯度提升算法

model_name = ['LogisticRegression',
              'RandomForestClassifier',
              "DecisionTreeClassifier",
              'MLPClassifier',
              'AdaBoostClassifier',
              'BaggingClassifier',
              'GradientBoostingClassifier']

acc = []        # 存放各算法的准确率
f1 = []         # 存放各算法的 f1 值
recall = []     # 存放各算法的召回率

for model in models:  # 训练每个算法
    model.fit(train_x, train_y)
    acc.append(model.score(test_x, test_y))
    y_pred = model.predict(test_x)
    f1.append(metrics.f1_score(y_pred, test_y))
    recall.append(metrics.recall_score(y_pred, test_y))

# 打印每种算法的评估结果
pd.DataFrame({"name": model_name, "acc": acc, "f1": f1, "recall": recall})

从上面的结果可知,除了决策树分类(DecisionTreeClassifier)和感知机分类(MLPClassifier)之外,大部分算法的准确率均超过了 90% 。

黑色星期五精准营销可视化分析

数据集预处理

本次挑战的数据集为 Kaggle 提供的黑色星期五零售商店交易情况。如果你想了解数据的相关信息,可以自行去 Kaggle 查阅相关的 数据描述 。现在先来加载数据,通过下面命令下载数据。

!wget -nc "https://labfile.oss.aliyuncs.com/courses/1363/BlackFriday.csv"

加载并预览数据集前 5 行。

import pandas as pd
import warnings
warnings.filterwarnings('ignore')

df = pd.read_csv('./BlackFriday.csv')
df.head()

从上面的输出结果我们可以看到,数据中主要为购买用户的一些基本信息。例如 User_ID 表示用户的 ID、Product_ID 表示用户所购买的产品的 ID、Gender 表示用户的性别、Age 表示用户的年龄等。下面我们通过可视化来分析该数据。
查看用 plotly 工具画出用户的男女比例饼状图。

import plotly.graph_objs as go
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=False)

temp = df["Gender"].value_counts()
trace = [go.Pie(labels=temp.index, values=temp.values)]
fig = go.Figure(data=trace)
iplot(fig)
image.png

从上面的结果可以看到,有四分之三的顾客都为男性用户。现在来分析这些顾客的年龄(Age)分布情况。
使用 Seaborn 工具绘制出不同性别的顾客用户的不同年龄分布。图的尺寸设置为(12,5)。

from matplotlib import pyplot as plt
import seaborn as sns
%matplotlib inline

fig1, ax1 = plt.subplots(figsize=(12, 5))
sns.countplot(df['Age'], hue=df['Gender'])
image.png

从可视化结果可以知道,大部分顾客的年龄分布在 26 到 35 岁之间。在数据集中 City_Category 表示城市类别,可取的值为 A 、B 和 C。数据集的最后一列Purchase 表示购买量。我们还可以分析一下三种类型城市的购买量。
求出每类城市的总购买量,并用饼状图将其进行画出。

temp = df.groupby('City_Category')['Purchase'].sum()
trace = [go.Pie(labels=temp.index, values=temp.values)]
fig = go.Figure(data=trace)
iplot(fig)
image.png

从上图中,我们可以看到 B 类城市的购买量最高。经过我们进行简单的可视化分析。已经可以大致了解数据中的基本信息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容