Pandas 2.2 中文官方教程和指南(二十五·二)(3)https://developer.aliyun.com/article/1509411
你可以通过将 driver
参数传递给 PyTables 来在内存中创建或加载 HDFStore。只有在关闭 HDFStore 时才会将更改写入磁盘。
In [211]: store = pd.HDFStore("test.h5", "w", driver="H5FD_CORE") In [212]: df = pd.DataFrame(np.random.randn(8, 3)) In [213]: store["test"] = df # only after closing the store, data is written to disk: In [214]: store.close() ```### 二进制文件 pandas 可以轻松接受 NumPy 记录数组,如果你需要读取由 C 结构数组组成的二进制文件。例如,给定一个名为 `main.c` 的 C 程序,在 64 位机器上使用 `gcc main.c -std=gnu99` 编译, ```py #include <stdio.h> #include <stdint.h> typedef struct _Data { int32_t count; double avg; float scale; } Data; int main(int argc, const char *argv[]) { size_t n = 10; Data d[n]; for (int i = 0; i < n; ++i) { d[i].count = i; d[i].avg = i + 1.0; d[i].scale = (float) i + 2.0f; } FILE *file = fopen("binary.dat", "wb"); fwrite(&d, sizeof(Data), n, file); fclose(file); return 0; }
以下 Python 代码将把二进制文件 'binary.dat'
读入 pandas 的 DataFrame
,其中结构的每个元素对应于框架中的一列:
names = "count", "avg", "scale" # note that the offsets are larger than the size of the type because of # struct padding offsets = 0, 8, 16 formats = "i4", "f8", "f4" dt = np.dtype({"names": names, "offsets": offsets, "formats": formats}, align=True) df = pd.DataFrame(np.fromfile("binary.dat", dt))
注意
结构元素的偏移量可能会因文件创建时所用机器的架构而异。不建议使用这种原始二进制文件格式进行通用数据存储,因为它不跨平台。我们建议使用 HDF5 或 parquet,这两种格式都受到 pandas 的 IO 功能支持。### CSV
CSV 文档
读取已压缩但不是由 gzip/bz2
(read_csv
理解的原生压缩格式)压缩的文件。此示例展示了一个 WinZipped
文件,但是是在上下文管理器中打开文件并使用该句柄进行读取的一般应用。点击这里查看
处理错误行 GH 2886
读取多个文件以创建单个 DataFrame
将多个文件合并为单个 DataFrame 的最佳方法是逐个读取各个框架,将所有单独的框架放入列表中,然后使用 pd.concat()
组合列表中的框架:
In [189]: for i in range(3): .....: data = pd.DataFrame(np.random.randn(10, 4)) .....: data.to_csv("file_{}.csv".format(i)) .....: In [190]: files = ["file_0.csv", "file_1.csv", "file_2.csv"] In [191]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)
您可以使用相同的方法来读取所有匹配模式的文件。以下是使用 glob
的示例:
In [192]: import glob In [193]: import os In [194]: files = glob.glob("file_*.csv") In [195]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)
最后,这种策略将适用于 io 文档 中描述的其他 pd.read_*(...)
函数。
解析多列中的日期组件
使用格式解析多列中的日期组件更快
In [196]: i = pd.date_range("20000101", periods=10000) In [197]: df = pd.DataFrame({"year": i.year, "month": i.month, "day": i.day}) In [198]: df.head() Out[198]: year month day 0 2000 1 1 1 2000 1 2 2 2000 1 3 3 2000 1 4 4 2000 1 5 In [199]: %timeit pd.to_datetime(df.year * 10000 + df.month * 100 + df.day, format='%Y%m%d') .....: ds = df.apply(lambda x: "%04d%02d%02d" % (x["year"], x["month"], x["day"]), axis=1) .....: ds.head() .....: %timeit pd.to_datetime(ds) .....: 4.01 ms +- 635 us per loop (mean +- std. dev. of 7 runs, 100 loops each) 1.05 ms +- 7.39 us per loop (mean +- std. dev. of 7 runs, 1,000 loops each)
在标题和数据之间跳过行
In [200]: data = """;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: date;Param1;Param2;Param4;Param5 .....: ;m²;°C;m²;m .....: ;;;; .....: 01.01.1990 00:00;1;1;2;3 .....: 01.01.1990 01:00;5;3;4;5 .....: 01.01.1990 02:00;9;5;6;7 .....: 01.01.1990 03:00;13;7;8;9 .....: 01.01.1990 04:00;17;9;10;11 .....: 01.01.1990 05:00;21;11;12;13 .....: """ .....:
选项 1:显式传递要跳过的行数
In [201]: from io import StringIO In [202]: pd.read_csv( .....: StringIO(data), .....: sep=";", .....: skiprows=[11, 12], .....: index_col=0, .....: parse_dates=True, .....: header=10, .....: ) .....: Out[202]: Param1 Param2 Param4 Param5 date 1990-01-01 00:00:00 1 1 2 3 1990-01-01 01:00:00 5 3 4 5 1990-01-01 02:00:00 9 5 6 7 1990-01-01 03:00:00 13 7 8 9 1990-01-01 04:00:00 17 9 10 11 1990-01-01 05:00:00 21 11 12 13
选项 2:先读取列名,然后读取数据
In [203]: pd.read_csv(StringIO(data), sep=";", header=10, nrows=10).columns Out[203]: Index(['date', 'Param1', 'Param2', 'Param4', 'Param5'], dtype='object') In [204]: columns = pd.read_csv(StringIO(data), sep=";", header=10, nrows=10).columns In [205]: pd.read_csv( .....: StringIO(data), sep=";", index_col=0, header=12, parse_dates=True, names=columns .....: ) .....: Out[205]: Param1 Param2 Param4 Param5 date 1990-01-01 00:00:00 1 1 2 3 1990-01-01 01:00:00 5 3 4 5 1990-01-01 02:00:00 9 5 6 7 1990-01-01 03:00:00 13 7 8 9 1990-01-01 04:00:00 17 9 10 11 1990-01-01 05:00:00 21 11 12 13
读取多个文件以创建单个 DataFrame
将多个文件合并为单个 DataFrame 的最佳方法是逐个读取各个框架,将所有单独的框架放入列表中,然后使用 pd.concat()
组合列表中的框架:
In [189]: for i in range(3): .....: data = pd.DataFrame(np.random.randn(10, 4)) .....: data.to_csv("file_{}.csv".format(i)) .....: In [190]: files = ["file_0.csv", "file_1.csv", "file_2.csv"] In [191]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)
您可以使用相同的方法来读取所有匹配模式的文件。以下是使用 glob
的示例:
In [192]: import glob In [193]: import os In [194]: files = glob.glob("file_*.csv") In [195]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)
最后,这种策略将适用于 io 文档 中描述的其他 pd.read_*(...)
函数。
解析多列中的日期组件
在多列中解析日期组件时,使用格式更快
In [196]: i = pd.date_range("20000101", periods=10000) In [197]: df = pd.DataFrame({"year": i.year, "month": i.month, "day": i.day}) In [198]: df.head() Out[198]: year month day 0 2000 1 1 1 2000 1 2 2 2000 1 3 3 2000 1 4 4 2000 1 5 In [199]: %timeit pd.to_datetime(df.year * 10000 + df.month * 100 + df.day, format='%Y%m%d') .....: ds = df.apply(lambda x: "%04d%02d%02d" % (x["year"], x["month"], x["day"]), axis=1) .....: ds.head() .....: %timeit pd.to_datetime(ds) .....: 4.01 ms +- 635 us per loop (mean +- std. dev. of 7 runs, 100 loops each) 1.05 ms +- 7.39 us per loop (mean +- std. dev. of 7 runs, 1,000 loops each)
在标题和数据之间跳过行
In [200]: data = """;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: ;;;; .....: date;Param1;Param2;Param4;Param5 .....: ;m²;°C;m²;m .....: ;;;; .....: 01.01.1990 00:00;1;1;2;3 .....: 01.01.1990 01:00;5;3;4;5 .....: 01.01.1990 02:00;9;5;6;7 .....: 01.01.1990 03:00;13;7;8;9 .....: 01.01.1990 04:00;17;9;10;11 .....: 01.01.1990 05:00;21;11;12;13 .....: """ .....:
选项 1:显式传递行以跳过行
In [201]: from io import StringIO In [202]: pd.read_csv( .....: StringIO(data), .....: sep=";", .....: skiprows=[11, 12], .....: index_col=0, .....: parse_dates=True, .....: header=10, .....: ) .....: Out[202]: Param1 Param2 Param4 Param5 date 1990-01-01 00:00:00 1 1 2 3 1990-01-01 01:00:00 5 3 4 5 1990-01-01 02:00:00 9 5 6 7 1990-01-01 03:00:00 13 7 8 9 1990-01-01 04:00:00 17 9 10 11 1990-01-01 05:00:00 21 11 12 13
选项 2:先读取列名,然后读取数据
In [203]: pd.read_csv(StringIO(data), sep=";", header=10, nrows=10).columns Out[203]: Index(['date', 'Param1', 'Param2', 'Param4', 'Param5'], dtype='object') In [204]: columns = pd.read_csv(StringIO(data), sep=";", header=10, nrows=10).columns In [205]: pd.read_csv( .....: StringIO(data), sep=";", index_col=0, header=12, parse_dates=True, names=columns .....: ) .....: Out[205]: Param1 Param2 Param4 Param5 date 1990-01-01 00:00:00 1 1 2 3 1990-01-01 01:00:00 5 3 4 5 1990-01-01 02:00:00 9 5 6 7 1990-01-01 03:00:00 13 7 8 9 1990-01-01 04:00:00 17 9 10 11 1990-01-01 05:00:00 21 11 12 13
选项 1:显式传递行以跳过行
In [201]: from io import StringIO In [202]: pd.read_csv( .....: StringIO(data), .....: sep=";", .....: skiprows=[11, 12], .....: index_col=0, .....: parse_dates=True, .....: header=10, .....: ) .....: Out[202]: Param1 Param2 Param4 Param5 date 1990-01-01 00:00:00 1 1 2 3 1990-01-01 01:00:00 5 3 4 5 1990-01-01 02:00:00 9 5 6 7 1990-01-01 03:00:00 13 7 8 9 1990-01-01 04:00:00 17 9 10 11 1990-01-01 05:00:00 21 11 12 13
选项 2:先读取列名,然后读取数据
In [203]: pd.read_csv(StringIO(data), sep=";", header=10, nrows=10).columns Out[203]: Index(['date', 'Param1', 'Param2', 'Param4', 'Param5'], dtype='object') In [204]: columns = pd.read_csv(StringIO(data), sep=";", header=10, nrows=10).columns In [205]: pd.read_csv( .....: StringIO(data), sep=";", index_col=0, header=12, parse_dates=True, names=columns .....: ) .....: Out[205]: Param1 Param2 Param4 Param5 date 1990-01-01 00:00:00 1 1 2 3 1990-01-01 01:00:00 5 3 4 5 1990-01-01 02:00:00 9 5 6 7 1990-01-01 03:00:00 13 7 8 9 1990-01-01 04:00:00 17 9 10 11 1990-01-01 05:00:00 21 11 12 13
SQL
SQL 文档
Excel
Excel 文档
仅加载可见工作表 GH 19842#issuecomment-892150745
HTML
HDFStore
HDFStores 文档
使用链接的多表层次结构管理异构数据 GH 3032
通过块对大型存储进行去重,本质上是一个递归减少操作。展示了一个从 csv 文件中接收数据并按块创建存储的函数,同时也进行了日期解析。点击这里查看
将属性存储到组节点
In [206]: df = pd.DataFrame(np.random.randn(8, 3)) In [207]: store = pd.HDFStore("test.h5") In [208]: store.put("df", df) # you can store an arbitrary Python object via pickle In [209]: store.get_storer("df").attrs.my_attribute = {"A": 10} In [210]: store.get_storer("df").attrs.my_attribute Out[210]: {'A': 10}
通过将 driver
参数传递给 PyTables,可以在内存中创建或加载 HDFStore。只有在关闭 HDFStore 时才会将更改写入磁盘。
In [211]: store = pd.HDFStore("test.h5", "w", driver="H5FD_CORE") In [212]: df = pd.DataFrame(np.random.randn(8, 3)) In [213]: store["test"] = df # only after closing the store, data is written to disk: In [214]: store.close()
二进制文件
如果需要读取由 C 结构数组组成的二进制文件,pandas 可以轻松接受 NumPy 记录数组。例如,给定一个名为 main.c
的文件中的 C 程序,在 64 位机器上使用 gcc main.c -std=gnu99
编译,
#include <stdio.h> #include <stdint.h> typedef struct _Data { int32_t count; double avg; float scale; } Data; int main(int argc, const char *argv[]) { size_t n = 10; Data d[n]; for (int i = 0; i < n; ++i) { d[i].count = i; d[i].avg = i + 1.0; d[i].scale = (float) i + 2.0f; } FILE *file = fopen("binary.dat", "wb"); fwrite(&d, sizeof(Data), n, file); fclose(file); return 0; }
以下 Python 代码将二进制文件 'binary.dat'
读入 pandas 的 DataFrame
中,结构的每个元素对应帧中的一列:
names = "count", "avg", "scale" # note that the offsets are larger than the size of the type because of # struct padding offsets = 0, 8, 16 formats = "i4", "f8", "f4" dt = np.dtype({"names": names, "offsets": offsets, "formats": formats}, align=True) df = pd.DataFrame(np.fromfile("binary.dat", dt))
注意
结构元素的偏移量可能因创建文件的机器架构而异。不建议使用这种原始二进制文件格式进行通用数据存储,因为它不跨平台。我们建议使用 HDF5 或 parquet,这两者都受到 pandas IO 设施的支持。
计算
相关性
通常,从 DataFrame.corr()
计算的相关矩阵的下三角形式(或上三角形式)是很有用的。可以通过向 where
传递布尔掩码来实现如下:
In [215]: df = pd.DataFrame(np.random.random(size=(100, 5))) In [216]: corr_mat = df.corr() In [217]: mask = np.tril(np.ones_like(corr_mat, dtype=np.bool_), k=-1) In [218]: corr_mat.where(mask) Out[218]: 0 1 2 3 4 0 NaN NaN NaN NaN NaN 1 -0.079861 NaN NaN NaN NaN 2 -0.236573 0.183801 NaN NaN NaN 3 -0.013795 -0.051975 0.037235 NaN NaN 4 -0.031974 0.118342 -0.073499 -0.02063 NaN
DataFrame.corr
中的 method
参数除了命名的相关性类型外还可以接受可调用对象。在这里,我们为 DataFrame
对象计算 距离相关性 矩阵。
In [219]: def distcorr(x, y): .....: n = len(x) .....: a = np.zeros(shape=(n, n)) .....: b = np.zeros(shape=(n, n)) .....: for i in range(n): .....: for j in range(i + 1, n): .....: a[i, j] = abs(x[i] - x[j]) .....: b[i, j] = abs(y[i] - y[j]) .....: a += a.T .....: b += b.T .....: a_bar = np.vstack([np.nanmean(a, axis=0)] * n) .....: b_bar = np.vstack([np.nanmean(b, axis=0)] * n) .....: A = a - a_bar - a_bar.T + np.full(shape=(n, n), fill_value=a_bar.mean()) .....: B = b - b_bar - b_bar.T + np.full(shape=(n, n), fill_value=b_bar.mean()) .....: cov_ab = np.sqrt(np.nansum(A * B)) / n .....: std_a = np.sqrt(np.sqrt(np.nansum(A ** 2)) / n) .....: std_b = np.sqrt(np.sqrt(np.nansum(B ** 2)) / n) .....: return cov_ab / std_a / std_b .....: In [220]: df = pd.DataFrame(np.random.normal(size=(100, 3))) In [221]: df.corr(method=distcorr) Out[221]: 0 1 2 0 1.000000 0.197613 0.216328 1 0.197613 1.000000 0.208749 2 0.216328 0.208749 1.000000
相关性
通常,从 DataFrame.corr()
计算的相关矩阵的下三角形式(或上三角形式)是很有用的。可以通过向 where
传递布尔掩码来实现如下:
In [215]: df = pd.DataFrame(np.random.random(size=(100, 5))) In [216]: corr_mat = df.corr() In [217]: mask = np.tril(np.ones_like(corr_mat, dtype=np.bool_), k=-1) In [218]: corr_mat.where(mask) Out[218]: 0 1 2 3 4 0 NaN NaN NaN NaN NaN 1 -0.079861 NaN NaN NaN NaN 2 -0.236573 0.183801 NaN NaN NaN 3 -0.013795 -0.051975 0.037235 NaN NaN 4 -0.031974 0.118342 -0.073499 -0.02063 NaN
DataFrame.corr
中的 method
参数除了命名的相关性类型外还可以接受可调用对象。在这里,我们为 DataFrame
对象计算 距离相关性 矩阵。
In [219]: def distcorr(x, y): .....: n = len(x) .....: a = np.zeros(shape=(n, n)) .....: b = np.zeros(shape=(n, n)) .....: for i in range(n): .....: for j in range(i + 1, n): .....: a[i, j] = abs(x[i] - x[j]) .....: b[i, j] = abs(y[i] - y[j]) .....: a += a.T .....: b += b.T .....: a_bar = np.vstack([np.nanmean(a, axis=0)] * n) .....: b_bar = np.vstack([np.nanmean(b, axis=0)] * n) .....: A = a - a_bar - a_bar.T + np.full(shape=(n, n), fill_value=a_bar.mean()) .....: B = b - b_bar - b_bar.T + np.full(shape=(n, n), fill_value=b_bar.mean()) .....: cov_ab = np.sqrt(np.nansum(A * B)) / n .....: std_a = np.sqrt(np.sqrt(np.nansum(A ** 2)) / n) .....: std_b = np.sqrt(np.sqrt(np.nansum(B ** 2)) / n) .....: return cov_ab / std_a / std_b .....: In [220]: df = pd.DataFrame(np.random.normal(size=(100, 3))) In [221]: df.corr(method=distcorr) Out[221]: 0 1 2 0 1.000000 0.197613 0.216328 1 0.197613 1.000000 0.208749 2 0.216328 0.208749 1.000000
时间增量
时间增量 文档。
In [222]: import datetime In [223]: s = pd.Series(pd.date_range("2012-1-1", periods=3, freq="D")) In [224]: s - s.max() Out[224]: 0 -2 days 1 -1 days 2 0 days dtype: timedelta64[ns] In [225]: s.max() - s Out[225]: 0 2 days 1 1 days 2 0 days dtype: timedelta64[ns] In [226]: s - datetime.datetime(2011, 1, 1, 3, 5) Out[226]: 0 364 days 20:55:00 1 365 days 20:55:00 2 366 days 20:55:00 dtype: timedelta64[ns] In [227]: s + datetime.timedelta(minutes=5) Out[227]: 0 2012-01-01 00:05:00 1 2012-01-02 00:05:00 2 2012-01-03 00:05:00 dtype: datetime64[ns] In [228]: datetime.datetime(2011, 1, 1, 3, 5) - s Out[228]: 0 -365 days +03:05:00 1 -366 days +03:05:00 2 -367 days +03:05:00 dtype: timedelta64[ns] In [229]: datetime.timedelta(minutes=5) + s Out[229]: 0 2012-01-01 00:05:00 1 2012-01-02 00:05:00 2 2012-01-03 00:05:00 dtype: datetime64[ns]
In [230]: deltas = pd.Series([datetime.timedelta(days=i) for i in range(3)]) In [231]: df = pd.DataFrame({"A": s, "B": deltas}) In [232]: df Out[232]: A B 0 2012-01-01 0 days 1 2012-01-02 1 days 2 2012-01-03 2 days In [233]: df["New Dates"] = df["A"] + df["B"] In [234]: df["Delta"] = df["A"] - df["New Dates"] In [235]: df Out[235]: A B New Dates Delta 0 2012-01-01 0 days 2012-01-01 0 days 1 2012-01-02 1 days 2012-01-03 -1 days 2 2012-01-03 2 days 2012-01-05 -2 days In [236]: df.dtypes Out[236]: A datetime64[ns] B timedelta64[ns] New Dates datetime64[ns] Delta timedelta64[ns] dtype: object
可以使用 np.nan 将值设置为 NaT,类似于 datetime
In [237]: y = s - s.shift() In [238]: y Out[238]: 0 NaT 1 1 days 2 1 days dtype: timedelta64[ns] In [239]: y[1] = np.nan In [240]: y Out[240]: 0 NaT 1 NaT 2 1 days dtype: timedelta64[ns]
创建示例数据
要从给定值的每个组合创建数据框,类似于 R 的expand.grid()
函数,我们可以创建一个字典,其中键是列名,值是数据值的列表:
In [241]: def expand_grid(data_dict): .....: rows = itertools.product(*data_dict.values()) .....: return pd.DataFrame.from_records(rows, columns=data_dict.keys()) .....: In [242]: df = expand_grid( .....: {"height": [60, 70], "weight": [100, 140, 180], "sex": ["Male", "Female"]} .....: ) .....: In [243]: df Out[243]: height weight sex 0 60 100 Male 1 60 100 Female 2 60 140 Male 3 60 140 Female 4 60 180 Male 5 60 180 Female 6 70 100 Male 7 70 100 Female 8 70 140 Male 9 70 140 Female 10 70 180 Male 11 70 180 Female
恒定系列
要评估一个系列是否具有恒定值,我们可以检查series.nunique() <= 1
。然而,一种更高效的方法,不需要首先计算所有唯一值,是:
In [244]: v = s.to_numpy() In [245]: is_constant = v.shape[0] == 0 or (s[0] == s).all()
此方法假定系列不包含缺失值。对于我们将删除 NA 值的情况,我们可以先简单地删除这些值:
In [246]: v = s.dropna().to_numpy() In [247]: is_constant = v.shape[0] == 0 or (s[0] == s).all()
如果缺失值被视为与任何其他值不同,则可以使用:
In [248]: v = s.to_numpy() In [249]: is_constant = v.shape[0] == 0 or (s[0] == s).all() or not pd.notna(v).any()
(请注意,此示例不区分np.nan
、pd.NA
和None
之间的差异)