Сравнение с SQL¶
Многие потенциальные пользователи pandas имеют некоторое представление о SQL. На этой странице приведены примеры того, как различные операции SQL будут выполняться в pandas.
Если вы новичок в pandas, вы можете сначала прочитать 10 Minutes to pandas, чтобы ознакомиться с библиотекой.
Как обычно, импортируем pandas и NumPy следующим образом:
In [1]: import pandas as pd
In [2]: import numpy as np
В большинстве примеров будет использоваться набор данных tips
, найденный в тестах pandas. Мы прочитаем данные в DataFrame под названием tips
и предположим, что у нас есть таблица базы данных с таким же именем и структурой.
In [3]: url = (
...: "https://raw.github.com/pandas-dev"
...: "/pandas/main/pandas/tests/io/data/csv/tips.csv"
...: )
...:
In [4]: tips = pd.read_csv(url)
In [5]: tips
Out[5]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
.. ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3
240 27.18 2.00 Female Yes Sat Dinner 2
241 22.67 2.00 Male Yes Sat Dinner 2
242 17.82 1.75 Male No Sat Dinner 2
243 18.78 3.00 Female No Thur Dinner 2
[244 rows x 7 columns]
Копии и операции на месте¶
Большинство операций pandas возвращают копии Series
/DataFrame
. Чтобы зафиксировать изменения, вам нужно либо назначить новую переменную:
sorted_df = df.sort_values("col1")
Либо перезаписать исходный объект:
df = df.sort_values("col1")
Примечание
Вы можете столкнуться с аргументом inplace=True
, доступным для некоторых методов:
df.sort_values("col1", inplace=True)
SELECT¶
В SQL выбор осуществляется с помощью списка столбцов, разделенных запятыми, которые вы хотите выбрать (или *
, чтобы выбрать все столбцы):
SELECT total_bill, tip, smoker, time
FROM tips;
В pandas выбор столбца выполняется путем передачи списка имен столбцов в ваш DataFrame:
In [6]: tips[["total_bill", "tip", "smoker", "time"]]
Out[6]:
total_bill tip smoker time
0 16.99 1.01 No Dinner
1 10.34 1.66 No Dinner
2 21.01 3.50 No Dinner
3 23.68 3.31 No Dinner
4 24.59 3.61 No Dinner
.. ... ... ... ...
239 29.03 5.92 No Dinner
240 27.18 2.00 Yes Dinner
241 22.67 2.00 Yes Dinner
242 17.82 1.75 No Dinner
243 18.78 3.00 No Dinner
[244 rows x 4 columns]
Вызов DataFrame без списка имен столбцов отобразит все столбцы (по аналогии с *
в SQL).
В SQL вы можете добавить вычисляемый столбец:
SELECT *, tip/total_bill as tip_rate
FROM tips;
В pandas вы можете использовать метод DataFrame.assign()
, чтобы добавить новый столбец в DataFrame:
In [7]: tips.assign(tip_rate=tips["tip"] / tips["total_bill"])
Out[7]:
total_bill tip sex smoker day time size tip_rate
0 16.99 1.01 Female No Sun Dinner 2 0.059447
1 10.34 1.66 Male No Sun Dinner 3 0.160542
2 21.01 3.50 Male No Sun Dinner 3 0.166587
3 23.68 3.31 Male No Sun Dinner 2 0.139780
4 24.59 3.61 Female No Sun Dinner 4 0.146808
.. ... ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3 0.203927
240 27.18 2.00 Female Yes Sat Dinner 2 0.073584
241 22.67 2.00 Male Yes Sat Dinner 2 0.088222
242 17.82 1.75 Male No Sat Dinner 2 0.098204
243 18.78 3.00 Female No Thur Dinner 2 0.159744
[244 rows x 8 columns]
WHERE¶
Фильтрация в SQL выполняется с помощью условия WHERE.
SELECT *
FROM tips
WHERE time = 'Dinner';
DataFrames можно фильтровать несколькими способами; наиболее интуитивным из них является использование логического индексирования.
In [1]: tips[tips["total_bill"] > 10]
Out[1]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
.. ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3
240 27.18 2.00 Female Yes Sat Dinner 2
241 22.67 2.00 Male Yes Sat Dinner 2
242 17.82 1.75 Male No Sat Dinner 2
243 18.78 3.00 Female No Thur Dinner 2
[227 rows x 7 columns]
Вышеприведенный оператор просто передает Series
со значениями True
/False
в DataFrame, возвращая все строки с True
.
In [1]: is_dinner = tips["time"] == "Dinner"
In [2]: is_dinner
Out[2]:
0 True
1 True
2 True
3 True
4 True
...
239 True
240 True
241 True
242 True
243 True
Name: time, Length: 244, dtype: bool
In [3]: is_dinner.value_counts()
Out[3]:
True 176
False 68
Name: time, dtype: int64
In [4]: tips[is_dinner]
Out[4]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
.. ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3
240 27.18 2.00 Female Yes Sat Dinner 2
241 22.67 2.00 Male Yes Sat Dinner 2
242 17.82 1.75 Male No Sat Dinner 2
243 18.78 3.00 Female No Thur Dinner 2
[176 rows x 7 columns]
Как с OR
и AND
в SQL, можно передать в DataFrame несколько условий с помощью |
(OR
) и &
(AND
).
Чаевые на сумму более 5 долларов за ужин:
SELECT *
FROM tips
WHERE time = 'Dinner' AND tip > 5.00;
In [5]: tips[(tips["time"] == "Dinner") & (tips["tip"] > 5.00)]
Out[5]:
total_bill tip sex smoker day time size
23 39.42 7.58 Male No Sat Dinner 4
44 30.40 5.60 Male No Sun Dinner 4
47 32.40 6.00 Male No Sun Dinner 4
52 34.81 5.20 Female No Sun Dinner 4
59 48.27 6.73 Male No Sat Dinner 4
116 29.93 5.07 Male No Sun Dinner 4
155 29.85 5.14 Female No Sun Dinner 5
170 50.81 10.00 Male Yes Sat Dinner 3
172 7.25 5.15 Male Yes Sun Dinner 2
181 23.33 5.65 Male Yes Sun Dinner 2
183 23.17 6.50 Male Yes Sun Dinner 4
211 25.89 5.16 Male Yes Sat Dinner 4
212 48.33 9.00 Male No Sat Dinner 4
214 28.17 6.50 Female Yes Sat Dinner 3
239 29.03 5.92 Male No Sat Dinner 3
Чаевые от вечеринок, состоящих не менее чем из 5 посетителей ИЛИ общая сумма счета в которых превышает 45 долларов:
SELECT *
FROM tips
WHERE size >= 5 OR total_bill > 45;
In [6]: tips[(tips["size"] >= 5) | (tips["total_bill"] > 45)]
Out[6]:
total_bill tip sex smoker day time size
59 48.27 6.73 Male No Sat Dinner 4
125 29.80 4.20 Female No Thur Lunch 6
141 34.30 6.70 Male No Thur Lunch 6
142 41.19 5.00 Male No Thur Lunch 5
143 27.05 5.00 Female No Thur Lunch 6
155 29.85 5.14 Female No Sun Dinner 5
156 48.17 5.00 Male No Sun Dinner 6
170 50.81 10.00 Male Yes Sat Dinner 3
182 45.35 3.50 Male Yes Sun Dinner 3
185 20.69 5.00 Male No Sun Dinner 5
187 30.46 2.00 Male Yes Sun Dinner 5
212 48.33 9.00 Male No Sat Dinner 4
216 28.15 3.00 Male Yes Sat Dinner 5
Проверка NULL выполняется с помощью методов notna()
и isna()
.
In [7]: frame = pd.DataFrame(
...: {"col1": ["A", "B", np.NaN, "C", "D"], "col2": ["F", np.NaN, "G", "H", "I"]}
...: )
...:
In [8]: frame
Out[8]:
col1 col2
0 A F
1 B NaN
2 NaN G
3 C H
4 D I
Предположим, у нас есть таблица той же структуры, что и наш DataFrame выше. Мы можем видеть только те записи, где в col2
значение IS NULL
со следующим запросом:
SELECT *
FROM frame
WHERE col2 IS NULL;
In [9]: frame[frame["col2"].isna()]
Out[9]:
col1 col2
1 B NaN
Получение элементов, где в col2
значение IS NOT NULL
, может быть выполнено с помощью notna()
.
SELECT *
FROM frame
WHERE col1 IS NOT NULL;
In [10]: frame[frame["col1"].notna()]
Out[10]:
col1 col2
0 A F
1 B NaN
3 C H
4 D I
GROUP BY¶
В pandas операции, подобные GROUP BY в SQL, выполняются с использованием метода с аналогичным названием groupby()
. groupby()
обычно относится к процессу, в котором мы хотели бы разделить набор данных на группы, применить некоторую функцию (обычно агрегацию), а затем объединить группы вместе.
Обычной операцией SQL будет получение количества записей в каждой группе по всему набору данных. Например, запрос, дающий нам количество чаевых, оставленных посетителями определенного пола:
SELECT sex, count(*)
FROM tips
GROUP BY sex;
/*
Female 87
Male 157
*/
Эквивалент в pandas будет такой:
In [11]: tips.groupby("sex").size()
Out[11]:
sex
Female 87
Male 157
dtype: int64
Обратите внимание, что в коде pandas мы использовали size()
, а не count()
. Это связано с тем, что count()
применяет функцию к каждому столбцу, возвращая количество записей с ненулевыми значениями в каждом.
In [12]: tips.groupby("sex").count()
Out[12]:
total_bill tip smoker day time size
sex
Female 87 87 87 87 87 87
Male 157 157 157 157 157 157
В качестве альтернативы мы могли бы применить метод count()
к отдельному столбцу:
In [13]: tips.groupby("sex")["total_bill"].count()
Out[13]:
sex
Female 87
Male 157
Name: total_bill, dtype: int64
Несколько функций можно применять одновременно. Скажем, мы хотели бы увидеть, как сумма чаевых различается в зависимости от дня недели. Метод 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
*/
In [14]: tips.groupby("day").agg({"tip": np.mean, "day": np.size})
Out[14]:
tip day
day
Fri 2.734737 19
Sat 2.993103 87
Sun 3.255132 76
Thur 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
*/
In [15]: tips.groupby(["smoker", "day"]).agg({"tip": [np.size, np.mean]})
Out[15]:
tip
size mean
smoker day
No Fri 4 2.812500
Sat 45 3.102889
Sun 57 3.167895
Thur 45 2.673778
Yes Fri 15 2.714000
Sat 42 2.875476
Sun 19 3.516842
Thur 17 3.030000
JOIN¶
Операцию, аналогичную JOIN
в SQL, можно выполнить с помощью join()
или merge()
. По умолчанию join()
объединяет DataFrame по их индексам. Каждый метод имеет параметры, позволяющие вам указать тип объдинения (LEFT
, RIGHT
, INNER
, FULL
) или столбцы для объединения (имена столбцов или их индексы).
Предупреждение
Если оба ключевых столбца содержат строки, в которых ключ является нулевым значением, эти строки будут сопоставляться друг с другом. Это отличается от обычного поведения SQL при объединении и может привести к неожиданным результатам.
In [16]: df1 = pd.DataFrame({"key": ["A", "B", "C", "D"], "value": np.random.randn(4)})
In [17]: df2 = pd.DataFrame({"key": ["B", "D", "D", "E"], "value": np.random.randn(4)})
Предположим, у нас есть две таблицы базы данных с тем же именем и структурой, что и у наших DataFrames.
Теперь давайте рассмотрим различные типы JOIN
.
INNER JOIN¶
SELECT *
FROM df1
INNER JOIN df2
ON df1.key = df2.key;
# merge performs an INNER JOIN by default
In [18]: pd.merge(df1, df2, on="key")
Out[18]:
key value_x value_y
0 B -0.282863 1.212112
1 D -1.135632 -0.173215
2 D -1.135632 0.119209
merge()
также предлагает параметры для случаев, когда вы хотите соединить один столбец DataFrame с индексом другого DataFrame.
In [19]: indexed_df2 = df2.set_index("key")
In [20]: pd.merge(df1, indexed_df2, left_on="key", right_index=True)
Out[20]:
key value_x value_y
1 B -0.282863 1.212112
3 D -1.135632 -0.173215
3 D -1.135632 0.119209
LEFT OUTER JOIN¶
Показать все записи из df1
.
SELECT *
FROM df1
LEFT OUTER JOIN df2
ON df1.key = df2.key;
In [21]: pd.merge(df1, df2, on="key", how="left")
Out[21]:
key value_x value_y
0 A 0.469112 NaN
1 B -0.282863 1.212112
2 C -1.509059 NaN
3 D -1.135632 -0.173215
4 D -1.135632 0.119209
RIGHT JOIN¶
Показать все записи из df2
.
SELECT *
FROM df1
RIGHT OUTER JOIN df2
ON df1.key = df2.key;
In [22]: pd.merge(df1, df2, on="key", how="right")
Out[22]:
key value_x value_y
0 B -0.282863 1.212112
1 D -1.135632 -0.173215
2 D -1.135632 0.119209
3 E NaN -1.044236
FULL JOIN¶
pandas также допускает FULL JOIN
, при котором отображаются обе стороны набора данных, независимо от того, нашлись ли совпадения для объединяемых столбцов. На момент написания FULL JOIN
поддерживается не во всех СУБД (MySQL).
Показать все записи из обеих таблиц.
SELECT *
FROM df1
FULL OUTER JOIN df2
ON df1.key = df2.key;
In [23]: pd.merge(df1, df2, on="key", how="outer")
Out[23]:
key value_x value_y
0 A 0.469112 NaN
1 B -0.282863 1.212112
2 C -1.509059 NaN
3 D -1.135632 -0.173215
4 D -1.135632 0.119209
5 E NaN -1.044236
UNION¶
UNION ALL
можно выполнить в pandas с помощью concat()
.
In [24]: df1 = pd.DataFrame(
....: {"city": ["Chicago", "San Francisco", "New York City"], "rank": range(1, 4)}
....: )
....:
In [25]: df2 = pd.DataFrame(
....: {"city": ["Chicago", "Boston", "Los Angeles"], "rank": [1, 4, 5]}
....: )
....:
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
*/
In [26]: pd.concat([df1, df2])
Out[26]:
city rank
0 Chicago 1
1 San Francisco 2
2 New York City 3
0 Chicago 1
1 Boston 4
2 Los Angeles 5
В SQL UNION
аналогичен UNION 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()
.
In [27]: pd.concat([df1, df2]).drop_duplicates()
Out[27]:
city rank
0 Chicago 1
1 San Francisco 2
2 New York City 3
1 Boston 4
2 Los Angeles 5
LIMIT¶
SELECT * FROM tips
LIMIT 10;
In [28]: tips.head(10)
Out[28]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
5 25.29 4.71 Male No Sun Dinner 4
6 8.77 2.00 Male No Sun Dinner 2
7 26.88 3.12 Male No Sun Dinner 4
8 15.04 1.96 Male No Sun Dinner 2
9 14.78 3.23 Male No Sun Dinner 2
Эквиваленты pandas для некоторых аналитических и агрегирующих функций SQL¶
Верхние n строк со смещением¶
-- MySQL
SELECT * FROM tips
ORDER BY tip DESC
LIMIT 10 OFFSET 5;
In [29]: tips.nlargest(10 + 5, columns="tip").tail(10)
Out[29]:
total_bill tip sex smoker day time size
183 23.17 6.50 Male Yes Sun Dinner 4
214 28.17 6.50 Female Yes Sat Dinner 3
47 32.40 6.00 Male No Sun Dinner 4
239 29.03 5.92 Male No Sat Dinner 3
88 24.71 5.85 Male No Thur Lunch 2
181 23.33 5.65 Male Yes Sun Dinner 2
44 30.40 5.60 Male No Sun Dinner 4
52 34.81 5.20 Female No Sun Dinner 4
85 34.83 5.17 Female No Thur Lunch 4
211 25.89 5.16 Male Yes Sat Dinner 4
Верхние 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;
In [30]: (
....: tips.assign(
....: rn=tips.sort_values(["total_bill"], ascending=False)
....: .groupby(["day"])
....: .cumcount()
....: + 1
....: )
....: .query("rn < 3")
....: .sort_values(["day", "rn"])
....: )
....:
Out[30]:
total_bill tip sex smoker day time size rn
95 40.17 4.73 Male Yes Fri Dinner 4 1
90 28.97 3.00 Male Yes Fri Dinner 2 2
170 50.81 10.00 Male Yes Sat Dinner 3 1
212 48.33 9.00 Male No Sat Dinner 4 2
156 48.17 5.00 Male No Sun Dinner 6 1
182 45.35 3.50 Male Yes Sun Dinner 3 2
197 43.11 5.00 Female Yes Thur Lunch 4 1
142 41.19 5.00 Male No Thur Lunch 5 2
То же самое с использованием функции rank(method='first')
In [31]: (
....: tips.assign(
....: rnk=tips.groupby(["day"])["total_bill"].rank(
....: method="first", ascending=False
....: )
....: )
....: .query("rnk < 3")
....: .sort_values(["day", "rnk"])
....: )
....:
Out[31]:
total_bill tip sex smoker day time size rnk
95 40.17 4.73 Male Yes Fri Dinner 4 1.0
90 28.97 3.00 Male Yes Fri Dinner 2 2.0
170 50.81 10.00 Male Yes Sat Dinner 3 1.0
212 48.33 9.00 Male No Sat Dinner 4 2.0
156 48.17 5.00 Male No Sun Dinner 6 1.0
182 45.35 3.50 Male Yes Sun Dinner 3 2.0
197 43.11 5.00 Female Yes Thur Lunch 4 1.0
142 41.19 5.00 Male No Thur Lunch 5 2.0
-- 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;
Давайте найдем чаевые с (rank < 3) для каждой гендерной группы, где (tips < 2). Обратите внимание, что при использовании rank(method='min')
функция rnk_min
остается неизменной для того же tip
(как функция Oracle RANK()
).
In [32]: (
....: tips[tips["tip"] < 2]
....: .assign(rnk_min=tips.groupby(["sex"])["tip"].rank(method="min"))
....: .query("rnk_min < 3")
....: .sort_values(["sex", "rnk_min"])
....: )
....:
Out[32]:
total_bill tip sex smoker day time size rnk_min
67 3.07 1.00 Female Yes Sat Dinner 1 1.0
92 5.75 1.00 Female Yes Fri Dinner 2 1.0
111 7.25 1.00 Female No Sat Dinner 1 1.0
236 12.60 1.00 Male Yes Sat Dinner 2 1.0
237 32.83 1.17 Male Yes Sat Dinner 2 2.0
UPDATE¶
UPDATE tips
SET tip = tip*2
WHERE tip < 2;
In [33]: tips.loc[tips["tip"] < 2, "tip"] *= 2
DELETE¶
DELETE FROM tips
WHERE tip > 9;
В pandas мы не удаляем ненужные строки, а оставляем нужные:
In [34]: tips = tips.loc[tips["tip"] <= 9]