日常数据分析任务中30个非常有用的Pandas函数

博文名称:30 Very Useful Pandas Functions for Everyday Data Analysis Tasks
博文链接:https://towardsdatascience.com/30-very-useful-pandas-functions-for-everyday-data-analysis-tasks-f1eae16409af
发表时间:Jan 5, 2022


image.png

Python的Pandas库是Python中使用最广泛的库。因为这是数据分析或机器学习的各个方面所必需的数据操作库。即使你正在从事数据可视化或机器学习,无论如何都会进行一些数据操作。在本文中,我将列出日常使用所需的Pandas函数,这些函数可以说足以满足常规的数据操作任务。

在本文中,我将使用Kaggle的一个公共数据集,称为FIFA数据集。此处提到了用户许可证。

请随时从此处下载数据集:

Datasets/fifa.csv at master · rashida048/Datasets

让我们开始操作数据吧!

导入必要的包和数据集:

import numpy as np
import pandas as pd
pd.set_option('display.max_columns', 100)

让我们开始讨论以下函数:

1. pd.read_csv, pd.read_excel

要提到的第一个函数是read_csv和read_excel。到现在为止,我在每个项目中都至少使用了其中一个功能。这些功能已经一目了然。它们用于将CSV或excel文件读取为Pandas的DataFrame格式。这里我使用read_csv函数来读取FIFA数据集:

df = pd.read_csv("fifa.csv")

有关read_csv函数的详细视频详见以下链接。它有几个很好的参数,可以帮助你在读取数据集时稍微清理一下数据集。我在这里有一篇详细的文章:

Import CSV Files As Pandas DataFrame With skiprows, skipfooter, usecols, index_col, and header…

也可以使用 .read_csv() 函数执行以下代码读取.txt文件:

data = pd.read_csv(file.txt, sep=" ")

如果你有一个excel文件而不是一个csv文件,你将使用pd.read_excel。
在read_csv或read_excel之后使用.head()函数来查看数据框的数据。默认情况下,它显示DataFrame的前5行。在这里,我展示了上面数据框df的前五行:

df.head()
image.png

如果你想要特定的行数而不是默认的行数,你可以指定它。如果我想要7行,我会在.head()函数中传入参数。

df.head(7)

2. df.columns

你有一个像这样的大数据集时,很难看到所有的列。使用.columns函数,你可以打印出数据集的所有列:

df.columns

输出:

Index(['Unnamed: 0', 'sofifa_id', 'player_url', 'short_name', 'long_name', 'age', 'dob', 'height_cm', 'weight_kg', 'nationality', 'club_name', 'league_name', 'league_rank', 'overall', 'potential', 'value_eur', 'wage_eur', 'player_positions', 'preferred_foot', 'international_reputation', 'weak_foot', 'skill_moves', 'work_rate', 'body_type', 'real_face', 'release_clause_eur', 'player_tags', 'team_position', 'team_jersey_number', 'loaned_from', 'joined', 'contract_valid_until', 'nation_position', 'nation_jersey_number', 'pace', 'shooting', 'passing', 'dribbling', 'defending', 'physic', 'gk_diving', 'gk_handling', 'gk_kicking', 'gk_reflexes', 'gk_speed', 'gk_positioning', 'player_traits', 'attacking_crossing', 'attacking_finishing', 'attacking_heading_accuracy', 'attacking_short_passing', 'attacking_volleys', 'skill_dribbling', 'skill_curve', 'skill_fk_accuracy', 'skill_long_passing', 'skill_ball_control', 'movement_acceleration', 'movement_sprint_speed', 'movement_agility', 'movement_reactions', 'movement_balance', 'power_shot_power', 'power_jumping', 'power_stamina', 'power_strength', 'power_long_shots', 'mentality_aggression', 'mentality_interceptions', 'mentality_positioning', 'mentality_vision', 'mentality_penalties', 
'mentality_composure', 'defending_marking', 'defending_standing_tackle', 'defending_sliding_tackle', 'goalkeeping_diving', 'goalkeeping_handling', 'goalkeeping_kicking', 'goalkeeping_positioning', 'goalkeeping_reflexes'], dtype='object')

3. df.drop()

你可以使用df.drop()删除一些不必要的列。在这个数据集中,我们有很多列,我们在本教程中不会使用所有列。因此,我们可以轻松删除一些:

df = df.drop(columns=['Unnamed: 0', 'weak_foot', 'real_face'])

我刚刚删除了这三列:'Unnamed: 0'、'weak_foot'、'real_face'。

4. .len()

提供DataFrame的长度。让我们看一个例子:

len(df)

输出:

16155

这个DataFrame有16155行数据。

5. df.query()

你可以使用布尔表达式进行过滤或查询。我将在此示例中使用“投篮”和“传球”列。在这里,我正在检查哪些行 'shooting' 大于 'passing'。

df.query("shooting > passing")

这将仅返回投篮大于传球的行。

6. df.iloc()

该函数将行和列索引作为参数,并相应地为你提供DataFrame的子集。在这里,我将提取前10行和列索引为5到10的数据:

df.iloc[:10, 5:10]
image.png

7. df.loc()

该函数执行的操作几乎与 .iloc() 函数类似。但是在这里,我们可以准确地指定我们想要的行索引以及列的名称。下面是一个例子:

df.loc[[3, 10, 14, 23], ['nationality', 'weight_kg', "height_cm"]]
image.png

查看行索引,我们只提取第 3、10、14和 23行。另外,对于列,我们只有指定的列。

8. df[‘’].dtypes

另一个非常基础和广泛使用的函数。因为在我们深入分析、可视化或预测建模之前,有必要了解变量的数据类型。我在这里使用 .dtypes函数获取“height_cm”列的数据类型:

df.height_cm.dtypes

输出:

dtype('int64')

你还可以选择使用以下代码获取每一列的数据类型:

df.dtypes

输出:

height_cm                     int64weight_kg                     int64nationality                  objectrandom_col                    int32club_name                    objectleague_name                  objectleague_rank                 float64overall                       int64potential                     int64value_eur                     int64wage_eur                      int64player_positions             objectpreferred_foot               objectinternational_reputation      int64skill_moves                   int64work_rate                    objectbody_type                    objectteam_position                objectteam_jersey_number          float64nation_position              objectnation_jersey_number        float64pace                        float64shooting                    float64passing                     float64dribbling                   float64defending                   float64physic                      float64cumsum_2                      int64rank_calc                   float64dtype: object

9. df.select_dtypes()

你可以使用此函数选择特定数据类型的变量或列。例如,我只想选择数据类型为“int64”的列。以下是如何做到这一点:

df.select_dtypes(include='int64')
image.png

我们得到了所有数据类型为“int64”的列。如果我们在 'select_dtypes' 函数中使用 'exclude' 而不是 'include',我们将得到数据类型不是 'int64' 类型的列:

df.select_dtypes(exclude='int64')
image.png

这是输出的一部分。可以看到,输出的变量不是整数。但是,你可能认为 'random_col' 列是整数。但是如果你检查它的数据类型,你会发现它看起来是整数,但实际它的数据类型是不同的。请随时检查。

10. df.insert()

顾名思义,它在指定位置插入一列。为了演示,我将首先创建一个跟我们数据框长度相同的随机数数组:

random_col = np.random.randint(100, size=len(df))

我将这个数组作为一列插入到数据框 df 的第 3 列位置。请记住,列索引从零开始。

df.insert(3, 'random_col', random_col)

查看数据框:

df.head()
image.png

可以看到, 'random_col' 列插入到第三列。

11. df[‘’].cumsum()

提供累计金额。让我用一个例子来解释。我将在此示例中使用“value_eur”和“wage_eur”列。下面是代码:

df[['value_eur', 'wage_eur']].cumsum()

输出:


image.png

正如你在每一行中看到的那样,它为你提供前一行所有值的累积总和。

12. df.sample()

当数据集太大时,可以从中提取代表性样本来执行分析和预测建模。这可能会节省你一些时间。此外,过多的数据有时可能会破坏可视化。我们可以使用此函数来获得一定数量的数据点或一定比例数据点。在这里,我从FIFA数据集中抽取了200个数据点的样本。它需要随机抽样。

df.sample(n = 200)

我在这里使用 25% 的 FIFA 数据集:

df.sample(frac = 0.25)

13. df[‘’].where()

此函数可帮助你根据布尔条件查询数据集。举个例子,我们之前添加的random_col列数值范围是0到100。 下面是我们看看哪些值大于50。

df['random_col'].where(df['random_col'] > 50)

输出:

0         NaN 
1         NaN 
2        56.0 
3         NaN 
4         NaN 
         ...  
16150    65.0 
16151     NaN 
16152     NaN 
16153    57.0 
16154     NaN 
Name: random_col, Length: 16155, dtype: float64

可以看到,如果值不满足不大于 50 的条件,则返回 NaN。我们可以使用以下代码将NaN替换为0或任何其他值:

df['random_col'].where(df['random_col'] > 50, 0)

输出:

0         0 
1         0 
2        56 
3         0 
4         0 
         .. 
16150    65 
16151     0 
16152     0 
16153    57 
16154     0 
Name: random_col, Length: 16155, dtype: int32

14. df[‘’].unique()

这对分类变量非常有用。它用于查找分类列的唯一值。让我们看看FIFA数据集中“skill_moves”列的唯一值是什么:

df.skill_moves.unique()

输出:

array([4, 5, 1, 3, 2], dtype=int64)

可以看到,Skill_moves列有五个唯一值。如果我们打印出数据集的头部来检查列的值,可能看不到其中的所有唯一值。所以,要知道所有的唯一值, .unique()函数真的很方便。

15. df[‘’].nunique()

另一个主流的函数。此函数可让你知道一列中有多少个唯一值。例如,如果你想查看此数据集中有多少不同的国籍,可以使用这行简单的代码:

df.nationality.nunique()

输出:

149

很棒的一点是,这个函数也可以用于整个数据集,以了解每列中唯一值的数量:

df.nunique()

输出:

height_cm                      48 
weight_kg                      54 
nationality                   149 
random_col                    100 
club_name                     577 
league_name                    37 
league_rank                     4 
overall                        53 
potential                      49 
value_eur                     161 
wage_eur                       41 
player_positions              907 
preferred_foot                  2 
international_reputation        5 
skill_moves                     5 
work_rate                       9 
body_type                       3 
team_position                  29 
team_jersey_number             99 
nation_position                28 
nation_jersey_number           26 
pace                           74 
shooting                       70 
passing                        67 
dribbling                      67 
defending                      69 
physic                         63 
cumsum_2                    14859 
rank_calc                     161 
dtype: int64

上面我们得到每列中唯一值的数目。

16. df[‘’].rank()

该函数为你提供基于特定列的排名。在 FIFA 数据集中,如果我们想根据 ‘value_eur’ 列对球员进行排名,代码如下:

df['rank_calc'] = df["value_eur"].rank()

使用上面的代码,创建了一个名为“rank_calc”的新列。这个新列将根据“value_eur”为你提供每个运动员的排名。默认情况下,该列将添加到最后。请自行运行该行代码进行检查。

17. .isin()

我将使用 .isin() 函数提取仅包含少数国籍的运动员的数据集子集。

nationality = ["Argentina", "Portugal", "Sweden", "England"] 
df[df.nationality.isin(nationality)]

运行这段代码,你将看到我们得到的数据集只包含上面列表中提到的几个国家。可以在此处看到提取的子集:


image.png

18. df.replace()

replace函数将替换列的值。当我们只需要替换列的一个唯一值时,我们只需要传递旧值和新值。现在,我们将“league_rank”1.0替换成1.1。

df.replace(1.0, 1.1)
image.png

现在看数据集中的League_rank列,1.0 被1.1取代。如果我们需要更改多个值,我们可以通过字典传递给替换函数,其中键应该是原始值,值应该是替换值。

df.replace({1.0: 1.1,  4.0: 4.1, 3.0: 3.1})
image.png

19. df.rename()

它用于对表的列重命名。在此,我将“weight_kg”和“height_cm”列更改为Weight (kg)”和“Height (cm)”:

df.rename(columns = {"weight_kg": "Weight (kg)", "height_cm": "Height (cm)"})
image.png

非常简单有用!

20. .fillna()

每当你在现实生活中收到一个大数据集时,在大多数情况下,都会有一些空值。得到一个完美的数据集很难。因此,如果你是数据分析师或数据科学家,空值填补是你日常任务的一部分。此函数 .fillna() 用你选择的其他值替换空值。以下是FIFA数据集末尾的一些列:


image.png

可以看到,在投篮、传球、防守和其他一些列中有一些空值。在开始进行任何预测建模和其他一些数据科学任务之前,我们确实需要用一些兼容数据类型的值替换这些空值。否则,我们可能会出错。例如,在‘pace’列中,值应该是数字,但你会看到NaN值。最通用但不是那么有效的方法是用零值替换这些NaN值。

df['pace'].fillna(0, inplace=True)
image.png

你会注意到,现在“pace”列中的NaN转为零。在“total pace”列中,如果有更多NaN值,它们也应该用零替换。
正如我之前提到的,用零替换可能不是最有效的方法。你可以将其替换为您选择的其他一些值。用平均值或中位数替换值也很常见。

df['pace'].fillna(df['pace'].mean(), inplace = True)

21. df.groupby()

这是最主流的数据汇总函数。可以根据某个变量对数据进行分组,并找出有关这些组的有用信息。例如,这里按国籍对数据进行分组并计算每个国籍的总“value_eur”:

df.groupby("nationality")['value_eur'].sum()

输出:

nationality
Albania                25860000
Algeria                70560000
Angola                  6070000
Antigua & Barbuda       1450000
Argentina            1281372000
                        ...    
Uzbekistan              7495000
Venezuela              41495000
Wales                 113340000
Zambia                  4375000
Zimbabwe                6000000
Name: value_eur, Length: 149, dtype: int64

阿尔巴尼亚所有球员的'value_eur'总和为25860000。
也可以按多个变量分组并使用多个聚合函数。我们将看到每个国籍和每个联赛排名的均值_eur、中值_eur、均值工资_eur和中值工资_eur。

df.groupby(['nationality', 'league_rank'])['value_eur', 'wage_eur'].agg([np.mean, np.median])

输出:


image.png

可以使用“group by”函数执行更多操作。我在这里有一篇详细的文章:

Efficient Data Summarizing and Analysis Using Pandas’ Groupby Function

22. .pct_change()

你可以计算变量较先前值的变化百分比。在本演示中,我将使用 value_eur列并获取每行数据相对于前一列的百分比变化。第一行将是NaN,因为之前没有要比较的值。

df.value_eur.pct_change()

输出:

0             NaN 
1       -0.213930 
2       -0.310127 
3       -0.036697 
4        0.209524 
           ...    
16150    0.000000 
16151    0.500000 
16152   -0.500000 
16153    0.000000 
16154   -1.000000 
Name: value_eur, Length: 16155, dtype: float64

在这个数据集中,你可能不觉得这很重要。
但想想一些财务数据。特别是当您每天拥有股票市场价值时。看到每天价值的百分比变化该有多好。

23. df.count()

它为你提供数据框中指定方向的数据数量。当方向为0时,它提供数据框列的数据数目:

df.count(0)

输出:

Unnamed: 0                 16155 
sofifa_id                  16155 
player_url                 16155 
short_name                 16155 
long_name                  16155 
                           ...   
goalkeeping_diving         16155 
goalkeeping_handling       16155 
goalkeeping_kicking        16155 
goalkeeping_positioning    16155 
goalkeeping_reflexes       16155 
Length: 81, dtype: int64

你可以看到每列中的数据数量。
当方向为1时,它提供行中的数据数:

df.count(1)

输出:

0        72 
1        72 
2        72 
3        72 
4        71 
         .. 
16150    68 
16151    68 
16152    68 
16153    68 
16154    69 
Length: 16155, dtype: int64

每一行的数据数量不同。如果你仔细观察数据集,你会发现它在几列中有很多空值。

24. df[‘’].value_counts()

我们可以使用此函数获取每个类别的值计数。在这里,我得到了每个League_rank中有多少个值。

df['league_rank'].value_counts()

输出:

1.0    11738 
2.0     2936 
3.0      639 
4.0      603 
Name: league_rank, dtype: int64

它返回默认排序的结果。如果你希望结果按升序排列,只需设置升序 = True:

df['league_rank'].value_counts(ascending=True)

输出:

4.0      603 
3.0      639 
2.0     2936 
1.0    11738 
Name: league_rank, dtype: int64

25. pd.crosstab()

它提供了一个频率表,它是两个变量的交叉表。我在这里制作了 League_rank 和 international_reputation 的交叉表:

pd.crosstab(df['league_rank'], df['international_reputation'])
image.png

所以,我们得到了League_rank和international_reputation所有组合的数量。我们可以看到大多数球员的international_reputation 和 League_rank都为 1。
可以进一步改进。我们可以在两个方向上添加累计值,必要时还可以获得标准化值:

pd.crosstab(df['league_rank'], df['international_reputation'],  
            margins = True, 
            margins_name="Total", 
            normalize = True)
image.png

下面是关于 count、value_counts 和 crosstab 方法的详细教程:

Three Very Useful Functions of Pandas to Summarize the Data

26. pd.qcut()

该函数根据数据的分布对数据进行分箱或分段。所以,我们得到了每个球员的分组。在这里,我将 value_eur分为5个部分,并获取哪个球员属于哪个部分:

pd.qcut(df['value_eur'], q = 5)

输出:

0        (1100000.0, 100500000.0] 
1        (1100000.0, 100500000.0] 
2        (1100000.0, 100500000.0] 
3        (1100000.0, 100500000.0] 
4        (1100000.0, 100500000.0] 
                   ...            
16150          (-0.001, 100000.0] 
16151          (-0.001, 100000.0] 
16152          (-0.001, 100000.0] 
16153          (-0.001, 100000.0] 
16154          (-0.001, 100000.0] 
Name: value_eur, Length: 16155, dtype: category 
Categories (5, interval[float64]): [(-0.001, 100000.0] < (100000.0, 230000.0] < (230000.0, 500000.0] < (500000.0, 1100000.0] < (1100000.0, 100500000.0]]

您可以使用上述代码行中的 value_counts 来查看球员如何落入哪个范围:

pd.qcut(df['value_eur'], q = 5).value_counts()

输出:

(-0.001, 100000.0]          3462 
(230000.0, 500000.0]        3305 
(100000.0, 230000.0]        3184 
(500000.0, 1100000.0]       3154 
(1100000.0, 100500000.0]    3050 
Name: value_eur, dtype: int64

如你所见,数字非常接近。默认情况下,qcut尝试将它们平均划分。但在现实生活中,它并不希望总是平等。因为大多数时候分布并不均匀。

27. pd.cut()

分档的另一种方法。如果我们想使用cut制作5个bin,它会将整个value_eur范围分成相等的五个部分,每个 bin 中的数目将相应地跟随。

pd.cut(df['value_eur'], bins = 5).value_counts()

输出:

(-100500.0, 20100000.0]      16102  
(20100000.0, 40200000.0]        40  
(40200000.0, 60300000.0]        10  
(60300000.0, 80400000.0]         2  
(80400000.0, 100500000.0]        1  
Name: value_eur, dtype: int64

每个区间的区间相等,但是每个分组的数目是非常不同的。

我有一篇关于使用 cut 和 qcut 方法进行数据分箱的详细文章。请随意查看:

Data Binning with Pandas Cut or Qcut Method

28. df[‘’].describe()

这是一个很好的函数,它提供了一些基本的统计度量。我在这里使用了wage_eur 列上的describe 函数:

df['wage_eur'].describe()

输出:

count     16155.000000 
mean      13056.453110 
std       23488.182571 
min           0.000000 
25%        2000.000000 
50%        5000.000000 
75%       10000.000000 
max      550000.000000 
Name: wage_eur, dtype: float64

如输出所示,我们有八种不同的度量。它们中的每一个都非常重要。

29. nlargest and nsmallest

这为你提供了具有指定变量的n个最大值或最小值的数据集。例如,我想获取前5位工资的行:

df.nlargest(5, "wage_eur")
image.png

以同样的方式,我可以用5个最小的wage_eur数据制作数据集的子集:

df.nsmallest(5, "wage_eur")
image.png

30. df.explode()

当某些行中有数据列表时,分解会很有用。当某些列中有整数而某些列中有列表时,很难分析、可视化或执行某些预测建模。Explode函数有助于分解这些列表。例如,看看这个数据框:

将dataframe按照某一指定列进行展开,使得原来的每一行展开成一行或多行。

df1 = pd.DataFrame({"city": ['A', 'B', 'C'], 
                   "day1": [22, 25, 21], 
                   'day2':[31, 12, 67], 
                   'day3': [27, 20, 15], 
                   'day4': [34, 37, [41, 45, 67, 90, 21]], 
                   'day5': [23, 54, 36]}) 
df1
image.png

让我们扩展d4列:

df1.explode(jupyter notebook'day4').reset_index(drop=True)
image.png

结论

Python的panda的库很强大,有这么多功能。我在日常生活中选择了一些重要的功能。如果你非常了解这些,你将能够成功执行大多数分析任务。Panda还有一个非常有用的函数,我在这里没有提到,那就是 .plot() 函数。你通过pandas就可绘图。Pandas在后端使用Matplotlib并为你返回绘图。我在这里有一个详细的教程:

An Ultimate Cheat Sheet for Data Visualization in Pandas

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