与 SQL 的比较#

由于许多潜在的 pandas 用户都熟悉 SQL ,因此本页旨在提供一些示例,说明如何使用 pandas 执行各种 SQL 操作。

如果你是 pandas 新手,你可能想先阅读 10 Minutes to pandas 来熟悉该库。

按照惯例,我们像这样导入 pandas 和 NumPy:

大多数示例将使用 pandas 测试中的 tips 数据集。我们将数据读入一个名为 tips 的 DataFrame,并假设我们有一个同名同结构的数据库表。

副本 vs. 原地操作#

大多数 pandas 操作会返回 Series/DataFrame 的副本。要使更改“生效”,您需要将其赋给一个新变量:

sorted_df = df.sort_values("col1")

或者覆盖原始变量:

df = df.sort_values("col1")

备注

您会看到一些方法提供了 inplace=Truecopy=False 关键字参数:

df.replace(5, inplace=True)

目前正在积极讨论弃用和删除大多数方法的 inplacecopy``(例如,``dropna),只保留一小部分方法(包括 replace)。在 Copy-on-Write 的上下文中,这两个关键字将不再需要。该提案可以在 here 找到。

SELECT#

在 SQL 中,选择是使用要选择的列的逗号分隔列表(或者使用 * 选择所有列)来完成的:

SELECT total_bill, tip, smoker, time
FROM tips;

在 pandas 中,列选择是通过将列名列表传递给 DataFrame 来完成的:

调用不带列名列表的 DataFrame 将显示所有列(类似于 SQL 的 *)。

在 SQL 中,您可以添加一个计算列:

SELECT *, tip/total_bill as tip_rate
FROM tips;

在 pandas 中,您可以使用 DataFrame 的 DataFrame.assign() 方法追加一个新列:

WHERE#

SQL 中的过滤是通过 WHERE 子句完成的。

SELECT *
FROM tips
WHERE time = 'Dinner';

DataFrame 可以通过多种方式进行过滤;其中最直观的是使用 boolean indexing

上面的语句只是将一个包含 True/False 对象的 Series 传递给 DataFrame,返回所有 True 的行。

就像 SQL 的 ORAND 一样,可以使用 |``(``OR)和 &``(``AND)将多个条件传递给 DataFrame。

晚餐时小费超过 5 美元:

SELECT *
FROM tips
WHERE time = 'Dinner' AND tip > 5.00;

聚会者人数至少为 5 人 或 账单总额超过 45 美元的餐费:

SELECT *
FROM tips
WHERE size >= 5 OR total_bill > 45;

NULL 检查使用 notna()isna() 方法完成。

假设我们有一个与上面 DataFrame 结构相同的表。我们可以使用以下查询仅查看 col2 IS NULL 的记录:

SELECT *
FROM frame
WHERE col2 IS NULL;

使用 notna() 可以获取 col1 IS NOT NULL 的项。

SELECT *
FROM frame
WHERE col1 IS NOT NULL;

GROUP BY#

在 pandas 中,SQL 的 GROUP BY 操作是通过同名的 groupby() 方法执行的。 groupby() 通常指的是一个过程,即我们将数据集拆分成组,应用某个函数(通常是聚合),然后将这些组组合起来。

在SQL中,一个常见的操作是获取数据集中每个分组的记录数。例如,以下查询用于获取按性别分的 the number of tips:

SELECT sex, count(*)
FROM tips
GROUP BY sex;
/*
Female     87
Male      157
*/

在pandas中,等效的操作是:

注意,在pandas代码中我们使用了 DataFrameGroupBy.size() 而不是 DataFrameGroupBy.count() 。这是因为 DataFrameGroupBy.count() 对每一列应用函数,返回其中 NOT NULL 记录的数量。

或者,我们也可以将 DataFrameGroupBy.count() 方法应用于单个列:

多个函数也可以一次性应用。例如,假设我们想看看每周的小费金额如何变化 - DataFrameGroupBy.agg() 允许你将一个字典传递给分组后的DataFrame,指定要对哪些列应用哪些函数。

SELECT day, AVG(tip), COUNT(*)
FROM tips
GROUP BY day;
/*
Fri   2.734737   19
Sat   2.993103   87
Sun   3.255132   76
Thu  2.771452   62
*/

通过将列的列表传递给 groupby() 方法,可以按多个列进行分组。

SELECT smoker, day, COUNT(*), AVG(tip)
FROM tips
GROUP BY smoker, day;
/*
smoker day
No     Fri      4  2.812500
       Sat     45  3.102889
       Sun     57  3.167895
       Thu    45  2.673778
Yes    Fri     15  2.714000
       Sat     42  2.875476
       Sun     19  3.516842
       Thu    17  3.030000
*/

JOIN#

可以使用 join()merge() 执行 JOIN 。默认情况下,join() 将在索引上连接DataFrame。每种方法都有参数,允许你指定要执行的连接类型(LEFTRIGHTINNERFULL)或要连接的列(列名或索引)。

警告

如果两个键列都包含键值为null的行,这些行将相互匹配。这与通常的SQL join行为不同,可能导致意外的结果。

假设我们有两个与我们的DataFrame同名同结构的数据库表。

现在我们来介绍各种 JOIN 类型。

INNER JOIN#

SELECT *
FROM df1
INNER JOIN df2
  ON df1.key = df2.key;

merge() 还提供了参数,用于连接一个DataFrame的列与另一个DataFrame的索引。

LEFT OUTER JOIN#

显示 df1 中的所有记录。

SELECT *
FROM df1
LEFT OUTER JOIN df2
  ON df1.key = df2.key;

RIGHT JOIN#

显示 df2 中的所有记录。

SELECT *
FROM df1
RIGHT OUTER JOIN df2
  ON df1.key = df2.key;

FULL JOIN#

pandas也支持 FULL JOIN,它显示数据集的双方,无论连接的列是否找到匹配项。截至目前, FULL JOIN 尚未在所有RDBMS(如MySQL)中得到支持。

显示两表中的所有记录。

SELECT *
FROM df1
FULL OUTER JOIN df2
  ON df1.key = df2.key;

UNION#

可以使用 concat() 执行 UNION ALL

SELECT city, rank
FROM df1
UNION ALL
SELECT city, rank
FROM df2;
/*
         city  rank
      Chicago     1
San Francisco     2
New York City     3
      Chicago     1
       Boston     4
  Los Angeles     5
*/

SQL的 UNIONUNION ALL 类似,但 UNION 会移除重复的行。

SELECT city, rank
FROM df1
UNION
SELECT city, rank
FROM df2;
-- notice that there is only one Chicago record this time
/*
         city  rank
      Chicago     1
San Francisco     2
New York City     3
       Boston     4
  Los Angeles     5
*/

在pandas中,你可以结合使用 concat()drop_duplicates()

LIMIT#

SELECT * FROM tips
LIMIT 10;

pandas中一些SQL分析和聚合函数的等效实现#

带偏移量的Top n行#

-- MySQL
SELECT * FROM tips
ORDER BY tip DESC
LIMIT 10 OFFSET 5;

每个分组的Top n行#

-- Oracle's ROW_NUMBER() analytic function
SELECT * FROM (
  SELECT
    t.*,
    ROW_NUMBER() OVER(PARTITION BY day ORDER BY total_bill DESC) AS rn
  FROM tips t
)
WHERE rn < 3
ORDER BY day, rn;

使用 rank(method='first') 函数的相同操作

-- Oracle's RANK() analytic function
SELECT * FROM (
  SELECT
    t.*,
    RANK() OVER(PARTITION BY sex ORDER BY tip) AS rnk
  FROM tips t
  WHERE tip < 2
)
WHERE rnk < 3
ORDER BY sex, rnk;

我们来找出对于 (tips < 2) 的情况下,按性别分组的 (rank < 3) 的小费。请注意,当使用 rank(method='min') 函数时,对于相同 tiprnk_min 保持不变(类似于Oracle的 RANK() 函数)。

UPDATE#

UPDATE tips
SET tip = tip*2
WHERE tip < 2;

DELETE#

DELETE FROM tips
WHERE tip > 9;

在pandas中,我们选择要保留的行,而不是删除它们: