Pandas数据分析学习笔记

前言

开刷Pandas数据分析,看起来很好理解,不过没做笔记没敲代码心里总是不安稳,所以复现下课程代码并演示其中遇到的问题,顺便水一水笔记好了

参考资料:

课程视频链接:Pandas数据分析从入门到实战

数据及代码示例:ant-learn-pandas: pandas学习课程代码仓库 (gitee.com)

一、数据读取

0. 数据类型

数据类型 说明 Pandas读取方法
csv, tsv, txt 用逗号、tab或其它字符分割的文本文件 read_csv
excel xls或xlsx文件 read_excel
mysql 关系型数据表 read_sql

1. read_csv

1.1 读取csv文件

csv是以逗号分割的文本文件,如下:

csv 复制代码
userId,movieId,rating,timestamp
1,1,4.0,964982703
1,3,4.0,964981247
1,6,4.0,964982224
1,47,5.0,964983815

直接使用read_csv读取该文件

py 复制代码
fpath = "../datas/ml-latest-small/ratings.csv"
# 读取
ratings = pd.read_csv(fpath)

1.2 指定分割符

已知access_pvuv.txt如下:

txt 复制代码
2019-09-10  139    92
2019-09-09  185    153
2019-09-08  123    59
2019-09-07  65     40
2019-09-06  157    98
2019-09-05  205    151
2019-09-04  196    167
2019-09-03  216    176
2019-09-02  227    148
2019-09-01  105    61

为read_csv添加参数delimiter (或seq) 指定分隔符,header=None表示没有第一行列名称

py 复制代码
fpath = "../datas/crazyant/access_pvuv.txt"

pvuv = pd.read_csv(
    fpath,
    delimiter='\t',
    header=None,
    names=['date', 'pv', 'uv']
)

2. read_excel

py 复制代码
fpath = "../datas/crazyant/access_pvuv.xlsx"
pvuv = pd.read_excel(fpath)
print(pvuv)

'''
输出结果
           日期   PV   UV
0  2019-09-10  139   92
1  2019-09-09  185  153
2  2019-09-08  123   59
3  2019-09-07   65   40
4  2019-09-06  157   98
5  2019-09-05  205  151
6  2019-09-04  196  167
7  2019-09-03  216  176
8  2019-09-02  227  148
9  2019-09-01  105   61
'''

3. read_sql

3.1 使用pymysql

连接数据库,选择编码方式

py 复制代码
import pandas as pd
import pymysql

conn = pymysql.connect(
    host='localhost',
    user='root',
    password='password',
    database='dbname',
    charset='utf8'
)
py 复制代码
table = pd.read_sql("select * from tbname", con=conn)
print(table)
'''
输出结果
   id                       preorder_traversal_string
0   1             4_2_1_0_#_#_#_3_#_#_8_7_#_#_11_#_#_
1   2  5_4_2_#_#_1_7_#_6_#_#_#_3_0_#_5_1_#_#_#_6_#_#_
2   3                  6_#_7_5_3_#_#_1_#_#_2_8_#_#_#_
'''

但是会报警告:

UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.

这里建议我们使用SQLAlchemy

3.2 使用SQLAlchemy

py 复制代码
import pandas as pd

from sqlalchemy import create_engine

host = "127.0.0.1"
user = "root"
password = "password"
database = "dbname"

engine = create_engine(f"mysql+pymysql://{user}:{password}@{host}/{database}")
sql = 'select * from tbname'
table = pd.read_sql(sql=sql, con=engine)

print(table)
'''
输出结果
   id                       preorder_traversal_string
0   1             4_2_1_0_#_#_#_3_#_#_8_7_#_#_11_#_#_
1   2  5_4_2_#_#_1_7_#_6_#_#_#_3_0_#_5_1_#_#_#_6_#_#_
2   3                  6_#_7_5_3_#_#_1_#_#_2_8_#_#_#_
'''

4. 查看数据格式

py 复制代码
# 查看前几行数据,默认为5行
print(ratings.head())
'''
输出结果
   userId  movieId  rating  timestamp
0       1        1     4.0  964982703
1       1        3     4.0  964981247
2       1        6     4.0  964982224
3       1       47     5.0  964983815
4       1       50     5.0  964982931
'''

# 查看数据的形状,返回(行数、列数)
print(ratings.shape)

'''
输出结果
(100836, 4)
'''

# 查看列名列表
print(ratings.columns)
'''
输出结果
Index(['userId', 'movieId', 'rating', 'timestamp'], dtype='object')
'''

# 查看索引列
print(ratings.index)
'''
输出结果
RangeIndex(start=0, stop=100836, step=1)
'''

# 查看每列的数据类型
print(ratings.dtypes)
'''
输出结果
userId         int64
movieId        int64
rating       float64
timestamp      int64
dtype: object
'''

二、数据结构

1. Series

py 复制代码
import pandas as pd

# 创建Series
s1 = pd.Series(list('abcd'))
print(s1)
'''
0    a
1    b
2    c
3    d
dtype: object
'''
py 复制代码
# 指定索引创建Series
s2 = pd.Series(list('efgh'), index=list('abcd'))
print(s2)
print(s2.index)
'''
a    e
b    f
c    g
d    h
dtype: object
Index(['a', 'b', 'c', 'd'], dtype='object')
'''
py 复制代码
# 字典创建Series
dict = {
    'a': 'e',
    'b': 'f',
    'c': 'g',
    'd': 'h'
}
s3 = pd.Series(dict)
print(s3)
'''
a    e
b    f
c    g
d    h
dtype: object
'''

2. DataFrame

py 复制代码
import pandas as pd

# 字典创建DataFrame
data = {
        'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
        'year': [2000, 2001, 2002, 2001, 2002],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9]
    }
df = pd.DataFrame(data)

print(df)
'''
    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9
'''
py 复制代码
# 输出DataFrame的索引、列标签以及数据类型
print(df.index, '\n\n', df.columns, '\n\n', df.dtypes)
'''
RangeIndex(start=0, stop=5, step=1)

 Index(['state', 'year', 'pop'], dtype='object')

 state     object
year       int64
pop      float64
dtype: object
'''

三、查询数据

0. 查询方法

  1. df.loc :基于标签索引,结果包含最后一个标签的值
  2. df.iloc:基于位置索引,结果不包含最后一个位置的值
  3. df.where
  4. df.query

本节主要介绍df.loc

1. 数据预处理

py 复制代码
import pandas as pd

# 数据预处理
df = pd.read_csv("../datas/beijing_tianqi/beijing_tianqi_2018.csv")

print(df.head())
'''
          ymd bWendu yWendu tianqi fengxiang fengli  aqi aqiInfo  aqiLevel
0  2018-01-01     3℃    -6℃   晴~多云       东北风   1-2级   59       良         2
1  2018-01-02     2℃    -5℃   阴~多云       东北风   1-2级   49       优         1
2  2018-01-03     2℃    -5℃     多云        北风   1-2级   28       优         1
3  2018-01-04     0℃    -8℃      阴       东北风   1-2级   28       优         1
4  2018-01-05     3℃    -6℃   多云~晴       西北风   1-2级   50       优   
'''
py 复制代码
# 设定索引为日期,方便按日期筛选
df.set_index('ymd', inplace=True)
py 复制代码
# 替换掉温度的后缀℃
df.loc[:, "bWendu"] = df["bWendu"].str.replace("℃", "").astype('int32')
df.loc[:, "yWendu"] = df["yWendu"].str.replace("℃", "").astype('int32')
# df[df.columns["bWendu"]] = df["bWendu"].str.replace("℃", "").astype('int32')
# df[df.columns["yWendu"]] = df["yWendu"].str.replace("℃", "").astype('int32')
py 复制代码
print(df.head())
'''
            bWendu  yWendu tianqi fengxiang fengli  aqi aqiInfo  aqiLevel
ymd
2018-01-01       3      -6   晴~多云       东北风   1-2级   59       良         2
2018-01-02       2      -5   阴~多云       东北风   1-2级   49       优         1
2018-01-03       2      -5     多云        北风   1-2级   28       优         1
2018-01-04       0      -8      阴       东北风   1-2级   28       优         1
2018-01-05       3      -6   多云~晴       西北风   1-2级   50       优    
'''

这里会报一个警告:

cmd 复制代码
 DeprecationWarning: In a future version, `df.iloc[:, i] = newvals` will attempt to set the 
 values inplace instead of always setting a new array. To retain the old behavior, use either
 `df[df.columns[i]] = newvals` or, if columns are non-unique, `df.isetitem(i, newvals)`

2. 按数值、列表、区间查询

py 复制代码
# 得到单个值
single_value = df.loc['2018-01-03', 'bWendu']
py 复制代码
# 得到一列/一行
s1 = df.loc['2018-01-03', ['bWendu', 'yWendu']]
s2 = df.loc[['2018-01-03', '2018-01-04', '2018-01-05'], 'bWendu']
py 复制代码
# 得到DataFrame
df2 = df.loc[['2018-01-03','2018-01-04','2018-01-05'], ['bWendu', 'yWendu']]
py 复制代码
# 按区间查询
df3 = df.loc['2018-01-03':'2018-01-05', 'bWendu':'fengxiang']

3. 条件查询

py 复制代码
# 查询最高温度小于30度,并且最低温度大于15度,并且是晴天,并且天气为优的数据
df4 = df.loc[(df["bWendu"] <= 30) & (df["yWendu"] >= 15)
        & (df["tianqi"] == '晴') & (df["aqiLevel"] == 1), :]
print(df4)
'''
            bWendu  yWendu tianqi fengxiang fengli  aqi aqiInfo  aqiLevel
ymd
2018-08-24      30      20      晴        北风   1-2级   40       优         1
2018-09-07      27      16      晴       西北风   3-4级   22       优         1
'''

其中,条件表达式返回的是一个布尔值的Series

py 复制代码
# 观察条件表达式
print(df["yWendu"] < -10)
'''
2018-01-01    False
2018-01-02    False
2018-01-03    False
2018-01-04    False
2018-01-05    False
              ...
2018-12-27     True
2018-12-28     True
2018-12-29     True
2018-12-30     True
2018-12-31    False
Name: yWendu, Length: 365, dtype: bool
'''

4. 函数查询

py 复制代码
# 直接写lambda表达式
df5 = df.loc[lambda df : (df["bWendu"] <= 30) & (df["yWendu"] >= 15), :]
py 复制代码
# 编写自己的函数,查询9月份,空气质量好的数据
def query_my_data(df):
    return df.index.str.startswith("2018-09") & (df["aqiLevel"] == 1)

df6 = df.loc[query_my_data, :]

四、新增数据列

1. 直接赋值

py 复制代码
df.loc[:, 'wencha'] = df['bWendu'] - df['yWendu']

2. df.apply

传入一个函数并选定axis:

  1. 当axis=1,函数的参数为一行的Series(常用)
  2. 当axis=0,函数的参数为一列的Series
py 复制代码
def get_wendu_type(x):
    if x["bWendu"] > 33:
        return '高温'
    if x["yWendu"] < -10:
        return '低温'
    return '常温'

df.loc[:, "wendu_type"] = df.apply(get_wendu_type, axis=1)

3. df.assign

df.assign总是会创建一个新的copy

利用lambda表达式,处理原来的数据得到新列

ini 复制代码
# 可以同时添加多个新的列
df.assign(
    yWendu_huashi = lambda x : x["yWendu"] * 9 / 5 + 32,
    # 摄氏度转华氏度
    bWendu_huashi = lambda x : x["bWendu"] * 9 / 5 + 32
)

4. 条件选择分组后赋值

py 复制代码
# 先创建空列(这是第一种创建新列的方法)
df['wencha_type'] = ''

错误示例:

py 复制代码
df.loc[df["bWendu"]-df["yWendu"]>10]["wencha_type"] = "温差大"

df.loc[df["bWendu"]-df["yWendu"]<=10]["wencha_type"] = "温差正常"

两个[]的链式操作相当于

py 复制代码
df.get(condition).set(wen_cha)

这里get得到的结果可能是view也可能是copy,存在歧义

正确示范:

py 复制代码
df.loc[df["bWendu"]-df["yWendu"]>10, "wencha_type"] = "温差大"

df.loc[df["bWendu"]-df["yWendu"]<=10, "wencha_type"] = "温差正常"

五、聚合查询

1. describe输出统计结果

py 复制代码
# 一下子提取所有数字列统计结果
df.describe()
cmd 复制代码
           bWendu      yWendu         aqi    aqiLevel      wencha
count  365.000000  365.000000  365.000000  365.000000  365.000000
mean    18.665753    8.358904   82.183562    2.090411   10.306849
std     11.858046   11.755053   51.936159    1.029798    2.781233
min     -5.000000  -12.000000   21.000000    1.000000    2.000000
25%      8.000000   -3.000000   46.000000    1.000000    8.000000
50%     21.000000    8.000000   69.000000    2.000000   10.000000
75%     29.000000   19.000000  104.000000    3.000000   12.000000
max     38.000000   27.000000  387.000000    6.000000   18.000000

describe只能得到数值列的统计结果

2. 非数值列统计

2.1 unique唯一去重

py 复制代码
print(df['tianqi'].unique())
cmd 复制代码
['晴~多云' '阴~多云' '多云' '阴' '多云~晴' '多云~阴' '晴' '阴~小雪' '小雪~多云' '小雨~ 阴' '小雨~雨夹雪'
 '多云~小雨' '小雨~多云' '大雨~小雨' '小雨' '阴~小雨' '多云~雷阵雨' '雷阵雨~多云' '阴~ 雷阵雨' '雷阵雨'
 '雷阵雨~大雨' '中雨~雷阵雨' '小雨~大雨' '暴雨~雷阵雨' '雷阵雨~中雨' '小雨~雷阵雨' '雷 阵雨~阴' '中雨~小雨'
 '小雨~中雨' '雾~多云' '霾']

2.2 value_counts按值计数

py 复制代码
print(df['wencha_type'].value_counts())
cmd 复制代码
温差正常    187
温差大     178
Name: wencha_type, dtype: int64

3. 协方差和相关系数

py 复制代码
print(df.cov(), '\n\n', df.corr())
cmd 复制代码
              bWendu      yWendu          aqi   aqiLevel     wencha
bWendu    140.613247  135.529633    47.462622   0.879204   5.083614
yWendu    135.529633  138.181274    16.186685   0.264165  -2.651641
aqi        47.462622   16.186685  2697.364564  50.749842  31.275937
aqiLevel    0.879204    0.264165    50.749842   1.060485   0.615038
wencha      5.083614   -2.651641    31.275937   0.615038   7.735255

             bWendu    yWendu       aqi  aqiLevel    wencha
bWendu    1.000000  0.972292  0.077067  0.071999  0.154142
yWendu    0.972292  1.000000  0.026513  0.021822 -0.081106
aqi       0.077067  0.026513  1.000000  0.948883  0.216523
aqiLevel  0.071999  0.021822  0.948883  1.000000  0.214740
wencha    0.154142 -0.081106  0.216523  0.214740  1.000000

六、缺失值处理

1. 处理方式

Pandas使用这些函数处理缺失值:

  • isnull和notnull:检测是否是空值,可用于df和series
  • dropna:丢弃、删除缺失值
    • axis : 删除行还是列,{0 or 'index', 1 or 'columns'}, default 0
    • how : 如果等于any则任何值为空都删除,如果等于all则所有值都为空才删除
    • inplace : 如果为True则修改当前df,否则返回新的df
  • fillna:填充空值
    • value:用于填充的值,可以是单个值,或者字典(key是列名,value是值)
    • method : 等于ffill使用前一个不为空的值填充forword fill;等于bfill使用后一个不为空的值填充backword fill
    • axis : 按行还是列填充,{0 or 'index', 1 or 'columns'}
    • inplace : 如果为True则修改当前df,否则返回新的df

2. 数据清洗示例

2.1 检测空值

py 复制代码
# 跳过前面两空行
studf = pd.read_excel("../datas/student_excel/student_excel.xlsx", skiprows=2)

# 检测空值
print(studf.isnull())
cmd 复制代码
    Unnamed: 0     姓名     科目     分数
0         True  False  False  False
1         True   True  False  False
2         True   True  False  False
3         True   True   True   True
4         True  False  False  False
5         True   True  False   True
6         True   True  False  False
7         True   True   True   True
8         True  False  False  False
9         True   True  False  False
10        True   True  False  False

2.2 dropna示例

py 复制代码
# 删除全为空的行和列
studf.dropna(axis=1, how='all', inplace=True)
studf.dropna(axis=0, how='all', inplace=True)

print(studf)
cmd 复制代码
     姓名  科目    分数
0    小明  语文  85.0
1   NaN  数学  80.0
2   NaN  英语  90.0
4    小王  语文  85.0
5   NaN  数学   NaN
6   NaN  英语  90.0
8    小刚  语文  85.0
9   NaN  数学  80.0
10  NaN  英语  90.0

2.3 fillna示例

py 复制代码
# 将空的分数填充为0
# 将空的姓名填充为上一个值
studf['分数'].fillna(value=0, inplace=True)
studf['姓名'].fillna(method='ffill', inplace=True)

print(studf)
cmd 复制代码
    姓名  科目    分数
0   小明  语文  85.0
1   小明  数学  80.0
2   小明  英语  90.0
4   小王  语文  85.0
5   小王  数学   0.0
6   小王  英语  90.0
8   小刚  语文  85.0
9   小刚  数学  80.0
10  小刚  英语  90.0

七、数据排序

1. 排序方法

  1. Series的排序:
    Series.sort_values(ascending=True, inplace=False)
    参数说明:
  • ascending:默认为True升序排序,为False降序排序
  • inplace:是否修改原始Series
  1. DataFrame的排序:
    DataFrame.sort_values(by, ascending=True, inplace=False)
    参数说明:
  • by:字符串或者List<字符串>,单列排序或者多列排序
  • ascending:bool或者bool的列表,升序还是降序,如果是list对应by的多列
  • inplace:是否修改原始DataFrame

2. 排序示例

py 复制代码
# Series的排序
print(df['aqi'].sort_values())
cmd 复制代码
ymd
2018-09-29     21
2018-10-09     21
2018-09-07     22
2018-09-30     22
2018-10-29     22
             ...
2018-11-14    266
2018-03-13    287
2018-04-02    287
2018-03-14    293
2018-03-28    387
Name: aqi, Length: 365, dtype: int64
py 复制代码
# DataFrame的排序
df.sort_values(by=['aqi', 'bWendu'], ascending=[False, True], inplace=True)
print(df[['aqi', 'bWendu']])
cmd 复制代码
            aqi  bWendu
ymd
2018-03-28  387      25
2018-03-14  293      15
2018-03-13  287      17
2018-04-02  287      26
2018-11-14  266      13
...         ...     ...
2018-10-29   22      15
2018-09-30   22      19
2018-09-07   22      27
2018-10-09   21      15
2018-09-29   21      22

八、字符串处理

0. Pandas的str

Pandas的字符串处理:

  1. 使用方法:先获取Series的str属性,然后在属性上调用函数;
  2. 只能在字符串列上使用,不能数字列上使用;
  3. Dataframe上没有str属性和处理方法
  4. Series.str并不是Python原生字符串,而是自己的一套方法,不过大部分和原生str很相似;

1. 基础用法

获取str属性,并调用各种方法,如replace, isnumeric, len

py 复制代码
print(df['wencha_type'].str.len())
cmd 复制代码
ymd
2018-03-28    3
2018-03-14    4
2018-03-13    3
2018-04-02    3
2018-11-14    4
             ..
2018-10-29    3
2018-09-30    4
2018-09-07    3
2018-10-09    3
2018-09-29    3
Name: wencha_type, Length: 365, dtype: int64

2. 条件查询

或使用contains, startswith等得到bool的Series做条件查询

py 复制代码
print(df.loc[df['tianqi'].str.startswith('多云'), ['tianqi', 'fengxiang']])
cmd 复制代码
           tianqi fengxiang
ymd
2018-03-28   多云~晴        东风
2018-03-14   多云~阴       东北风
2018-04-02     多云        北风
2018-11-14     多云        南风
2018-11-26     多云       东南风
...           ...       ...
2018-01-25     多云       东北风
2018-10-10   多云~晴       西北风
2018-02-03     多云        北风
2018-09-30     多云       西北风
2018-10-09   多云~晴       西北风

3. 正则表达式

由于Series.str天然支持正则表达式,示例如下:

匹配字符集合并做替换:

py 复制代码
# 添加新列
def get_nianyueri(x):
    year,month,day = x["ymd"].split("-")
    return f"{year}年{month}月{day}日"
df["中文日期"] = df.apply(get_nianyueri, axis=1)

# 尝试将 年 月 日 去除
df.loc[:, '中文日期'] = df['中文日期'].str.replace('[年月日]', '')
print(df['中文日期'])
cmd 复制代码
86     20180328
72     20180314
71     20180313
91     20180402
317    20181114
         ...
301    20181029
272    20180930
249    20180907
281    20181009
271    20180929
Name: 中文日期, Length: 365, dtype: object

捕获组提取数据:

py 复制代码
extracted_fengli = df['fengli'].str.extract(r'(\d)-(\d)')
print(extracted_fengli.head())
cmd 复制代码
     0  1
86   1  2
72   1  2
71   1  2
91   1  2
317  1  2
..  .. ..
301  3  4
272  4  5
249  3  4
281  4  5
271  3  4

九、索引

0. 索引的作用

选择恰当的索引可以加速查询性能

  1. 当索引是唯一的时,Pandas会用哈希表优化性能,时间复杂度为O(1)
  2. 当索引不唯一,但是单调时,Pandas会使用二分查找,时间复杂度为O(log n)
  3. 当索引既不唯一且不单调时,Pandas只能遍历,时间复杂度为O(n)

因此,我们要判断当前索引是否为以上类型,尽可能选择唯一的索引,单调次之

1. 选择索引示例

原始数据如下:

py 复制代码
print(df.head())
cmd 复制代码
   userId  movieId  rating  timestamp
0       1        1     4.0  964982703
1       1        3     4.0  964981247
2       1        6     4.0  964982224
3       1       47     5.0  964983815
4       1       50     5.0  964982931

判断每一列是否存在唯一约束:

py 复制代码
print(df.nunique() == len(df))
cmd 复制代码
userId       False
movieId      False
rating       False
timestamp    False
dtype: bool

判断每一列是否单调:

py 复制代码
# 使用这一句会报FutureWarning
# is_monotonic = df.apply(lambda x: x.is_monotonic)

is_monotonic_increasing = df.apply(lambda x: x.is_monotonic_increasing)
is_monotonic_decreasing = df.apply(lambda x: x.is_monotonic_decreasing)
print(is_monotonic_increasing, '\n\n', is_monotonic_decreasing)
cmd 复制代码
userId        True
movieId      False
rating       False
timestamp    False
dtype: bool

 userId       False
movieId      False
rating       False
timestamp    False
dtype: bool

2. 设置索引示例

DataFrame.set_index(keys, append=False, drop=True, inplace=False)

keys代表被用作索引的列

append代表是否保留原来的索引

drop表示是否将指定的列在原数据列中删除

inplace表示是否在原数据上修改

py 复制代码
df.set_index('userId',append=True, drop=False, inplace=True)
print(df.head())
cmd 复制代码
          userId  movieId  rating  timestamp
  userId
0 1            1        1     4.0  964982703
1 1            1        3     4.0  964981247
2 1            1        6     4.0  964982224
3 1            1       47     5.0  964983815
4 1            1       50     5.0  964982931
相关推荐
博观而约取5 分钟前
Django ORM 1. 创建模型(Model)
数据库·python·django
王小王-1231 小时前
基于Hadoop的京东厨具商品数据分析及商品价格预测系统的设计与实现
hadoop·数据分析·京东厨具·厨具分析·商品分析
精灵vector1 小时前
构建专家级SQL Agent交互
python·aigc·ai编程
Zonda要好好学习2 小时前
Python入门Day2
开发语言·python
Vertira2 小时前
pdf 合并 python实现(已解决)
前端·python·pdf
太凉2 小时前
Python之 sorted() 函数的基本语法
python
项目題供诗2 小时前
黑马python(二十四)
开发语言·python
可观测性用观测云2 小时前
Cloudflare 日志采集和分析最佳实践
数据分析
晓13133 小时前
OpenCV篇——项目(二)OCR文档扫描
人工智能·python·opencv·pycharm·ocr
是小王同学啊~3 小时前
(LangChain)RAG系统链路向量检索器之Retrievers(五)
python·算法·langchain