1. Pandas從小白到大師學(xué)習(xí)指南

        共 12011字,需瀏覽 25分鐘

         ·

        2020-12-14 04:14

        作者:Rudolf H?hn

        機(jī)器之心編譯

        在本文中,作者從 Pandas 的簡(jiǎn)介開(kāi)始,一步一步講解了 Pandas 的發(fā)展現(xiàn)狀、內(nèi)存優(yōu)化等問(wèn)題。這是一篇最佳實(shí)踐教程,既適合用過(guò) Pandas 的讀者,也適合沒(méi)用過(guò)但想要上手的小白。


        通過(guò)本文,你將有望發(fā)現(xiàn)一到多種用 pandas 編碼的新方法。


        本文包括以下內(nèi)容:


        • Pandas 發(fā)展現(xiàn)狀

        • 內(nèi)存優(yōu)化

        • 索引

        • 方法鏈

        • 隨機(jī)提示


        在閱讀本文時(shí),我建議你閱讀每個(gè)你不了解的函數(shù)的文檔字符串(docstrings)。簡(jiǎn)單的 Google 搜索和幾秒鐘 Pandas 文檔的閱讀,都會(huì)使你的閱讀體驗(yàn)更加愉快。


        Pandas 的定義和現(xiàn)狀


        什么是 Pandas?


        Pandas 是一個(gè)「開(kāi)源的、有 BSD 開(kāi)源協(xié)議的庫(kù),它為 Python 編程語(yǔ)言提供了高性能、易于使用的數(shù)據(jù)架構(gòu)以及數(shù)據(jù)分析工具」??傊峁┝吮环Q(chēng)為 DataFrame 和 Series(對(duì)那些使用 Panel 的人來(lái)說(shuō),它們已經(jīng)被棄用了)的數(shù)據(jù)抽象,通過(guò)管理索引來(lái)快速訪問(wèn)數(shù)據(jù)、執(zhí)行分析和轉(zhuǎn)換運(yùn)算,甚至可以繪圖(用 matplotlib 后端)。


        Pandas 的當(dāng)前最新版本是 v0.25.0?
        (https://github.com/pandas-dev/pandas/releases/tag/v0.25.0)



        Pandas 正在逐步升級(jí)到 1.0 版,而為了達(dá)到這一目的,它改變了很多人們習(xí)以為常的細(xì)節(jié)。Pandas 的核心開(kāi)發(fā)者之一 Marc Garcia 發(fā)表了一段非常有趣的演講——「走向 Pandas 1.0」。


        演講鏈接:https://www.youtube.com/watch?v=hK6o_TDXXN8


        用一句話來(lái)總結(jié),Pandas v1.0 主要改善了穩(wěn)定性(如時(shí)間序列)并刪除了未使用的代碼庫(kù)(如 SparseDataFrame)。


        數(shù)據(jù)


        讓我們開(kāi)始吧!選擇「1985 到 2016 年間每個(gè)國(guó)家的自殺率」作為玩具數(shù)據(jù)集。這個(gè)數(shù)據(jù)集足夠簡(jiǎn)單,但也足以讓你上手 Pandas。


        數(shù)據(jù)集鏈接:https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016


        在深入研究代碼之前,如果你想重現(xiàn)結(jié)果,要先執(zhí)行下面的代碼準(zhǔn)備數(shù)據(jù),確保列名和類(lèi)型是正確的。


        import pandas as pdimport numpy as npimport os# to download https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016
        data_path = 'path/to/folder/'df = (pd.read_csv(filepath_or_buffer=os.path.join(data_path, 'master.csv')) .rename(columns={'suicides/100k pop' : 'suicides_per_100k', ' gdp_for_year ($) ' : 'gdp_year', 'gdp_per_capita ($)' : 'gdp_capita', 'country-year' : 'country_year'}) .assign(gdp_year=lambda _df: _df['gdp_year'].str.replace(',','').astype(np.int64)) )

        提示:如果你讀取了一個(gè)大文件,在?
        read_csv(https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html)中參數(shù)設(shè)定為 chunksize=N,這會(huì)返回一個(gè)可以輸出 DataFrame 對(duì)象的迭代器。


        這里有一些關(guān)于這個(gè)數(shù)據(jù)集的描述:


        >>> df.columnsIndex(['country', 'year', 'sex', 'age', 'suicides_no', 'population', 'suicides_per_100k', 'country_year', 'HDI for year', 'gdp_year', 'gdp_capita', 'generation'], dtype='object')

        這里有 101 個(gè)國(guó)家、年份從 1985 到 2016、兩種性別、六個(gè)年代以及六個(gè)年齡組。有一些獲得這些信息的方法:


        可以用 unique() 和 nunique() 獲取列內(nèi)唯一的值(或唯一值的數(shù)量);


        >>> df['generation'].unique()array(['Generation X', 'Silent', 'G.I. Generation', 'Boomers', 'Millenials', 'Generation Z'], dtype=object)>>> df['country'].nunique()101

        可以用 describe() 輸出每一列不同的統(tǒng)計(jì)數(shù)據(jù)(例如最小值、最大值、平均值、總數(shù)等),如果指定 include='all',會(huì)針對(duì)每一列目標(biāo)輸出唯一元素的數(shù)量和出現(xiàn)最多元素的數(shù)量;


        可以用 head() 和 tail() 來(lái)可視化數(shù)據(jù)框的一小部分。


        通過(guò)這些方法,你可以迅速了解正在分析的表格文件。


        內(nèi)存優(yōu)化


        在處理數(shù)據(jù)之前,了解數(shù)據(jù)并為數(shù)據(jù)框的每一列選擇合適的類(lèi)型是很重要的一步。


        在內(nèi)部,Pandas 將數(shù)據(jù)框存儲(chǔ)為不同類(lèi)型的 numpy 數(shù)組(比如一個(gè) float64 矩陣,一個(gè) int32 矩陣)。


        有兩種可以大幅降低內(nèi)存消耗的方法。


        import pandas as pd
        def mem_usage(df: pd.DataFrame) -> str: """This method styles the memory usage of a DataFrame to be readable as MB. Parameters ---------- df: pd.DataFrame Data frame to measure. Returns ------- str Complete memory usage as a string formatted for MB. """ return f'{df.memory_usage(deep=True).sum() / 1024 ** 2 : 3.2f} MB'
        def convert_df(df: pd.DataFrame, deep_copy: bool = True) -> pd.DataFrame: """Automatically converts columns that are worth stored as ``categorical`` dtype. Parameters ---------- df: pd.DataFrame Data frame to convert. deep_copy: bool Whether or not to perform a deep copy of the original data frame. Returns ------- pd.DataFrame Optimized copy of the input data frame. """ return df.copy(deep=deep_copy).astype({ col: 'category' for col in df.columns if df[col].nunique() / df[col].shape[0] < 0.5})


        Pandas 提出了一種叫做 memory_usage() 的方法,這種方法可以分析數(shù)據(jù)框的內(nèi)存消耗。在代碼中,指定 deep=True 來(lái)確??紤]到了實(shí)際的系統(tǒng)使用情況。


        memory_usage():https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.memory_usage.html


        了解列的類(lèi)型
        https://pandas.pydata.org/pandas-docs/stable/getting_started/basics.html#basics-dtypes
        很重要。它可以通過(guò)兩種簡(jiǎn)單的方法節(jié)省高達(dá) 90% 的內(nèi)存使用:


        • 了解數(shù)據(jù)框使用的類(lèi)型;

        • 了解數(shù)據(jù)框可以使用哪種類(lèi)型來(lái)減少內(nèi)存的使用(例如,price 這一列值在 0 到 59 之間,只帶有一位小數(shù),使用 float64 類(lèi)型可能會(huì)產(chǎn)生不必要的內(nèi)存開(kāi)銷(xiāo))


        除了降低數(shù)值類(lèi)型的大?。ㄓ?int32 而不是 int64)外,Pandas 還提出了分類(lèi)類(lèi)型:https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html

        如果你是用 R 語(yǔ)言的開(kāi)發(fā)人員,你可能覺(jué)得它和 factor 類(lèi)型是一樣的。


        這種分類(lèi)類(lèi)型允許用索引替換重復(fù)值,還可以把實(shí)際值存在其他位置。教科書(shū)中的例子是國(guó)家。和多次存儲(chǔ)相同的字符串「瑞士」或「波蘭」比起來(lái),為什么不簡(jiǎn)單地用 0 和 1 替換它們,并存儲(chǔ)在字典中呢?


        categorical_dict = {0: 'Switzerland', 1: 'Poland'}

        Pandas 做了幾乎相同的工作,同時(shí)添加了所有的方法,可以實(shí)際使用這種類(lèi)型,并且仍然能夠顯示國(guó)家的名稱(chēng)。


        回到 convert_df() 方法,如果這一列中的唯一值小于 50%,它會(huì)自動(dòng)將列類(lèi)型轉(zhuǎn)換成 category。這個(gè)數(shù)是任意的,但是因?yàn)閿?shù)據(jù)框中類(lèi)型的轉(zhuǎn)換意味著在 numpy 數(shù)組間移動(dòng)數(shù)據(jù),因此我們得到的必須比失去的多。


        接下來(lái)看看數(shù)據(jù)中會(huì)發(fā)生什么。


        >>> mem_usage(df)10.28 MB>>> mem_usage(df.set_index(['country', 'year', 'sex', 'age']))5.00 MB>>> mem_usage(convert_df(df))1.40 MB>>> mem_usage(convert_df(df.set_index(['country', 'year', 'sex', 'age'])))1.40 MB

        通過(guò)使用「智能」轉(zhuǎn)換器,數(shù)據(jù)框使用的內(nèi)存幾乎減少了 10 倍(準(zhǔn)確地說(shuō)是 7.34 倍)。


        索引


        Pandas 是強(qiáng)大的,但也需要付出一些代價(jià)。當(dāng)你加載 DataFrame 時(shí),它會(huì)創(chuàng)建索引并將數(shù)據(jù)存儲(chǔ)在 numpy 數(shù)組中。這是什么意思?一旦加載了數(shù)據(jù)框,只要正確管理索引,就可以快速地訪問(wèn)數(shù)據(jù)。


        訪問(wèn)數(shù)據(jù)的方法主要有兩種,分別是通過(guò)索引和查詢?cè)L問(wèn)。根據(jù)具體情況,你只能選擇其中一種。但在大多數(shù)情況中,索引(和多索引)都是最好的選擇。我們來(lái)看下面的例子:


        >>> %%time>>> df.query('country == "Albania" and year == 1987 and sex == "male" and age == "25-34 years"')CPU times: user 7.27 ms, sys: 751 μs, total: 8.02 ms# ==================>>> %%time>>> mi_df.loc['Albania', 1987, 'male', '25-34 years']CPU times: user 459 μs, sys: 1 μs, total: 460 μs

        什么?加速 20 倍?


        你要問(wèn)自己了,創(chuàng)建這個(gè)多索引要多長(zhǎng)時(shí)間?


        %%timemi_df = df.set_index(['country', 'year', 'sex', 'age'])CPU times: user 10.8 ms, sys: 2.2 ms, total: 13 ms


        通過(guò)查詢?cè)L問(wèn)數(shù)據(jù)的時(shí)間是 1.5 倍。如果你只想檢索一次數(shù)據(jù)(這種情況很少發(fā)生),查詢是正確的方法。否則,你一定要堅(jiān)持用索引,CPU 會(huì)為此感激你的。


        .set_index(drop=False) 允許不刪除用作新索引的列。


        .loc[]/.iloc[] 方法可以很好地讀取數(shù)據(jù)框,但無(wú)法修改數(shù)據(jù)框。如果需要手動(dòng)構(gòu)建(比如使用循環(huán)),那就要考慮其他的數(shù)據(jù)結(jié)構(gòu)了(比如字典、列表等),在準(zhǔn)備好所有數(shù)據(jù)后,創(chuàng)建 DataFrame。否則,對(duì)于 DataFrame 中的每一個(gè)新行,Pandas 都會(huì)更新索引,這可不是簡(jiǎn)單的哈希映射。


        >>> (pd.DataFrame({'a':range(2), 'b': range(2)}, index=['a', 'a']) .loc['a'])   a ba 0 0a 1 1


        因此,未排序的索引可以降低性能。為了檢查索引是否已經(jīng)排序并對(duì)它排序,主要有兩種方法:


        %%time>>> mi_df.sort_index()CPU times: user 34.8 ms, sys: 1.63 ms, total: 36.5 ms>>> mi_df.index.is_monotonicTrue

        更多詳情請(qǐng)參閱:


        • Pandas 高級(jí)索引用戶指南:https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html;

        • Pandas 庫(kù)中的索引代碼:https://github.com/pandas-dev/pandas/blob/master/pandas/core/indexing.py。


        方法鏈


        使用 DataFrame 的方法鏈?zhǔn)擎溄佣鄠€(gè)返回 DataFrame 方法的行為,因此它們都是來(lái)自 DataFrame 類(lèi)的方法。在現(xiàn)在的 Pandas 版本中,使用方法鏈?zhǔn)菫榱瞬淮鎯?chǔ)中間變量并避免出現(xiàn)如下情況:


        import numpy as npimport pandas as pddf = pd.DataFrame({'a_column': [1, -999, -999], 'powerless_column': [2, 3, 4], 'int_column': [1, 1, -1]}) df['a_column'] = df['a_column'].replace(-999, np.nan) df['power_column'] = df['powerless_column'] ** 2 df['real_column'] = df['int_column'].astype(np.float64) df = df.apply(lambda _df: _df.replace(4, np.nan)) df?=?df.dropna(how='all')

        用下面的鏈替換:


        df = (pd.DataFrame({'a_column': [1, -999, -999], 'powerless_column': [2, 3, 4], 'int_column': [1, 1, -1]}) .assign(a_column=lambda _df: _df['a_column'].replace(-999, np.nan)) .assign(power_column=lambda _df: _df['powerless_column'] ** 2) .assign(real_column=lambda _df: _df['int_column'].astype(np.float64)) .apply(lambda _df: _df.replace(4, np.nan)) .dropna(how='all') )

        說(shuō)實(shí)話,第二段代碼更漂亮也更簡(jiǎn)潔。



        方法鏈的工具箱是由不同的方法(比如 apply、assign、loc、query、pipe、groupby 以及 agg)組成的,這些方法的輸出都是 DataFrame 對(duì)象或 Series 對(duì)象(或 DataFrameGroupBy)。


        了解它們最好的方法就是實(shí)際使用。舉個(gè)簡(jiǎn)單的例子:


        (df .groupby('age') .agg({'generation':'unique'}) .rename(columns={'generation':'unique_generation'})# Recommended from v0.25#?.agg(unique_generation=('generation',?'unique')))
        獲得每個(gè)年齡范圍中所有唯一年代標(biāo)簽的簡(jiǎn)單鏈


        在得到的數(shù)據(jù)框中,「年齡」列是索引。


        除了了解到「X 代」覆蓋了三個(gè)年齡組外,分解這條鏈。第一步是對(duì)年齡組分組。這一方法返回了一個(gè) DataFrameGroupBy 對(duì)象,在這個(gè)對(duì)象中,通過(guò)選擇組的唯一年代標(biāo)簽聚合了每一組。


        在這種情況下,聚合方法是「unique」方法,但它也可以接受任何(匿名)函數(shù)。


        在 0.25 版本中,Pandas 引入了使用 agg 的新方法:
        https://dev.pandas.io/whatsnew/v0.25.0.html#groupby-aggregation-with-relabeling。


        (df .groupby(['country', 'year']) .agg({'suicides_per_100k': 'sum'}) .rename(columns={'suicides_per_100k':'suicides_sum'})# Recommended from v0.25# .agg(suicides_sum=('suicides_per_100k', 'sum')) .sort_values('suicides_sum', ascending=False) .head(10))
        用排序值(sort_values)和 head 得到自殺率排前十的國(guó)家和年份


        (df .groupby(['country', 'year']) .agg({'suicides_per_100k': 'sum'}) .rename(columns={'suicides_per_100k':'suicides_sum'})# Recommended from v0.25# .agg(suicides_sum=('suicides_per_100k', 'sum')) .nlargest(10, columns='suicides_sum'))
        用排序值 nlargest 得到自殺率排前十的國(guó)家和年份


        在這些例子中,輸出都是一樣的:有兩個(gè)指標(biāo)(國(guó)家和年份)的 MultiIndex 的 DataFrame,還有包含排序后的 10 個(gè)最大值的新列 suicides_sum。


        「國(guó)家」和「年份」列是索引。


        nlargest(10) 比 sort_values(ascending=False).head(10) 更有效。

        另一個(gè)有趣的方法是 unstack:
        https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.unstack.html,這種方法允許轉(zhuǎn)動(dòng)索引水平。


        (mi_df .loc[('Switzerland', 2000)] .unstack('sex')?[['suicides_no',?'population']])

        「age」是索引,列「suicides_no」和「population」都有第二個(gè)水平列「sex」。


        下一個(gè)方法 pipe 是最通用的方法之一。這種方法允許管道運(yùn)算(就像在 shell 腳本中)執(zhí)行比鏈更多的運(yùn)算。


        管道的一個(gè)簡(jiǎn)單但強(qiáng)大的用法是記錄不同的信息。


        def log_head(df, head_count=10):     print(df.head(head_count))     return df
        def log_columns(df): print(df.columns) return df
        def log_shape(df): print(f'shape = {df.shape}') return df
        和 pipe 一起使用的不同記錄函數(shù)。


        舉個(gè)例子,我們想驗(yàn)證和 year 列相比,country_year 是否正確:


        (df .assign(valid_cy=lambda _serie: _serie.apply( lambda _row: re.split(r'(?=\d{4})', _row['country_year'])[1] == str(_row['year']), axis=1)) .query('valid_cy == False') .pipe(log_shape))
        用來(lái)驗(yàn)證「country_year」列中年份的管道。


        管道的輸出是 DataFrame,但它也可以在標(biāo)準(zhǔn)輸出(console/REPL)中打印。


        shape = (0, 13)

        你也可以在一條鏈中用不同的 pipe。


        (df .pipe(log_shape) .query('sex == "female"') .groupby(['year', 'country']) .agg({'suicides_per_100k':'sum'}) .pipe(log_shape) .rename(columns={'suicides_per_100k':'sum_suicides_per_100k_female'})# Recommended from v0.25# .agg(sum_suicides_per_100k_female=('suicides_per_100k', 'sum')) .nlargest(n=10, columns=['sum_suicides_per_100k_female']))
        女性自殺數(shù)量最高的國(guó)家和年份。


        生成的 DataFrame 如下所示:

        索引是「年份」和「國(guó)家」。


        標(biāo)準(zhǔn)輸出的打印如下所示:


        shape = (27820, 12)shape = (2321, 1)


        除了記錄到控制臺(tái)外,pipe 還可以直接在數(shù)據(jù)框的列上應(yīng)用函數(shù)。


        from sklearn.preprocessing import MinMaxScaler
        def norm_df(df, columns): return df.assign(**{col: MinMaxScaler().fit_transform(df[[col]].values.astype(float)) for col in columns})
        for?sex?in?['male',?'female']:? print(sex)? print(?df?.query(f'sex?==?"{sex}"')? .groupby(['country'])? .agg({'suicides_per_100k':?'sum',?'gdp_year':?'mean'})? .rename(columns={'suicides_per_100k':'suicides_per_100k_sum',??'gdp_year':?'gdp_year_mean'}) #?Recommended?in?v0.25 #?.agg(suicides_per_100k=('suicides_per_100k_sum',?'sum'),? #?gdp_year=('gdp_year_mean',?'mean'))? .pipe(norm_df,?columns=['suicides_per_100k_sum',?'gdp_year_mean'])? .corr(method='spearman')?)? print('\n')
        自殺數(shù)量是否和 GDP 的下降相關(guān)?是否和性別相關(guān)?


        上面的代碼在控制臺(tái)中的打印如下所示:


        male????????            suicides_per_100k_sum?gdp_year_meansuicides_per_100k_sum?      1.000000?        0.421218gdp_year_mean???????????????0.421218?        1.000000

        female?????????????????????suicides_per_100k_sum?gdp_year_meansuicides_per_100k_sum????????1.000000?        0.452343gdp_year_mean????????????????0.452343?        1.000000

        深入研究代碼。norm_df() 將一個(gè) DataFrame 和用 MinMaxScaling 擴(kuò)展列的列表當(dāng)做輸入。使用字典理解,創(chuàng)建一個(gè)字典 {column_name: method, …},然后將其解壓為 assign() 函數(shù)的參數(shù) (colunmn_name=method, …)。


        在這種特殊情況下,min-max 縮放不會(huì)改變對(duì)應(yīng)的輸出:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html,它僅用于參數(shù)。


        在(遙遠(yuǎn)的?)未來(lái),緩式評(píng)估(lazy evaluation)可能出現(xiàn)在方法鏈中,所以在鏈上做一些投資可能是一個(gè)好想法。

        最后(隨機(jī))的技巧


        下面的提示很有用,但不適用于前面的任何部分:


        itertuples() 可以更高效地遍歷數(shù)據(jù)框的行;


        >>> %%time>>>?for?row?in?df.iterrows():?continueCPU?times:?user?1.97?s,?sys:?17.3?ms,?total:?1.99?s>>>?for?tup?in?df.itertuples():?continueCPU?times:?user?55.9?ms,?sys:?2.85?ms,?total:?58.8?ms


        注意:tup 是一個(gè) namedtuple


        join() 用了 merge();
        在 Jupyter 筆記本中,在代碼塊的開(kāi)頭寫(xiě)上 %%time,可以有效地測(cè)量時(shí)間;
        UInt8 類(lèi):https://pandas.pydata.org/pandas-docs/stable/user_guide/gotchas.html#support-for-integer-na支持帶有整數(shù)的 NaN 值

        記住,任何密集的 I/O(例如展開(kāi)大型 CSV 存儲(chǔ))用低級(jí)方法都會(huì)執(zhí)行得更好(盡可能多地用 Python 的核心函數(shù))。


        還有一些本文沒(méi)有涉及到的有用的方法和數(shù)據(jù)結(jié)構(gòu),這些方法和數(shù)據(jù)結(jié)構(gòu)都很值得花時(shí)間去理解:


        數(shù)據(jù)透視表:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.pivot.html?source=post_page

        時(shí)間序列/日期功能:https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html?source=post_page

        繪圖:https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html?source=post_page


        總結(jié)


        希望你可以因?yàn)檫@篇簡(jiǎn)短的文章,更好地理解 Pandas 背后的工作原理,以及 Pandas 庫(kù)的發(fā)展現(xiàn)狀。本文還展示了不同的用于優(yōu)化數(shù)據(jù)框內(nèi)存以及快速分析數(shù)據(jù)的工具。希望對(duì)現(xiàn)在的你來(lái)說(shuō),索引和查找的概念能更加清晰。最后,你還可以試著用方法鏈寫(xiě)更長(zhǎng)的鏈。


        這里還有一些筆記:https://github.com/unit8co/medium-pandas-wan?source=post_page


        除了文中的所有代碼外,還包括簡(jiǎn)單數(shù)據(jù)索引數(shù)據(jù)框(df)和多索引數(shù)據(jù)框(mi_df)性能的定時(shí)指標(biāo)。


        熟能生巧,所以繼續(xù)修煉技能,并幫助我們建立一個(gè)更好的世界吧。


        PS:有時(shí)候純用 Numpy 會(huì)更快。

        原文鏈接:https://medium.com/unit8-machine-learning-publication/from-pandas-wan-to-pandas-master-4860cf0ce442
        PS:如果覺(jué)得分享內(nèi)容有一些幫助,歡迎大家隨手分享、點(diǎn)贊、在看。
        < END >

        瀏覽 57
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 欧美精品系列 | 国产视频一区二区在线观看 | 涩涩视频在线观看免费 | 亚洲一区二区三区无码 | 国产 做受视频 |