{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Основы программирования в Python\n",
"\n",
"*Алла Тамбовцева, НИУ ВШЭ*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Объединение датафреймов `pandas`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Представим, что у нас есть две таблицы, два датафрейма `pandas`, и мы хотим объединить их по строкам. Например, у нас есть данные, выкачанные с сайта за прошлую неделю, и данные, выкачанные с него же за текущую неделю. Столбцы у нас в обоих датафреймах одинаковые, нужно просто добавить новые наблюдения в датафрейм с данными за прошлую неделю."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"df1 = pd.DataFrame([[1, 2, 3], [4, 7, 8], [0, 8, 9]], \n",
" columns=[\"A\", \"B\", \"C\"])"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"df2 = pd.DataFrame([[11, 12, 13], [10, 18, 19]], \n",
" columns=[\"A\", \"B\", \"C\"])"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" A | \n",
" B | \n",
" C | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1 | \n",
" 2 | \n",
" 3 | \n",
"
\n",
" \n",
" | 1 | \n",
" 4 | \n",
" 7 | \n",
" 8 | \n",
"
\n",
" \n",
" | 2 | \n",
" 0 | \n",
" 8 | \n",
" 9 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" A B C\n",
"0 1 2 3\n",
"1 4 7 8\n",
"2 0 8 9"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df1"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" A | \n",
" B | \n",
" C | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 11 | \n",
" 12 | \n",
" 13 | \n",
"
\n",
" \n",
" | 1 | \n",
" 10 | \n",
" 18 | \n",
" 19 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" A B C\n",
"0 11 12 13\n",
"1 10 18 19"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df2"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" A | \n",
" B | \n",
" C | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1 | \n",
" 2 | \n",
" 3 | \n",
"
\n",
" \n",
" | 1 | \n",
" 4 | \n",
" 7 | \n",
" 8 | \n",
"
\n",
" \n",
" | 2 | \n",
" 0 | \n",
" 8 | \n",
" 9 | \n",
"
\n",
" \n",
" | 0 | \n",
" 11 | \n",
" 12 | \n",
" 13 | \n",
"
\n",
" \n",
" | 1 | \n",
" 10 | \n",
" 18 | \n",
" 19 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" A B C\n",
"0 1 2 3\n",
"1 4 7 8\n",
"2 0 8 9\n",
"0 11 12 13\n",
"1 10 18 19"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full = pd.concat([df1, df2]) # список датафреймов\n",
"full "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"По умолчанию функция `concat()` «склеивает» датафреймы по строкам, если бы мы захотели объединить их по столбцам, то понадобилось бы указать ось, добавив аргумент `axis`. Для примера добавим к датафрейму `full` еще два столбца со значениями из `df3`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" D | \n",
" E | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 22 | \n",
" 33 | \n",
"
\n",
" \n",
" | 1 | \n",
" 44 | \n",
" 45 | \n",
"
\n",
" \n",
" | 2 | \n",
" 46 | \n",
" 10 | \n",
"
\n",
" \n",
" | 3 | \n",
" 11 | \n",
" 14 | \n",
"
\n",
" \n",
" | 4 | \n",
" 18 | \n",
" 21 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" D E\n",
"0 22 33\n",
"1 44 45\n",
"2 46 10\n",
"3 11 14\n",
"4 18 21"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df3 = pd.DataFrame([[22, 33], [44, 45], [46, 10], \n",
" [11, 14], [18, 21]], \n",
" columns=['D', 'E'])\n",
"df3"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"ename": "InvalidIndexError",
"evalue": "Reindexing only valid with uniquely valued Index objects",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mInvalidIndexError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfull2\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconcat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mfull\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdf3\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/reshape/concat.py\u001b[0m in \u001b[0;36mconcat\u001b[0;34m(objs, axis, join, join_axes, ignore_index, keys, levels, names, verify_integrity, sort, copy)\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[0mkeys\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlevels\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mlevels\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnames\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mnames\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 224\u001b[0m \u001b[0mverify_integrity\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mverify_integrity\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 225\u001b[0;31m copy=copy, sort=sort)\n\u001b[0m\u001b[1;32m 226\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 227\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/reshape/concat.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, objs, axis, join, join_axes, keys, levels, names, ignore_index, verify_integrity, copy, sort)\u001b[0m\n\u001b[1;32m 376\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 377\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 378\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnew_axes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_new_axes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 379\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 380\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/reshape/concat.py\u001b[0m in \u001b[0;36m_get_new_axes\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 443\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mi\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 444\u001b[0m \u001b[0;32mcontinue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 445\u001b[0;31m \u001b[0mnew_axes\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_comb_axis\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 446\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 447\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin_axes\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mndim\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/reshape/concat.py\u001b[0m in \u001b[0;36m_get_comb_axis\u001b[0;34m(self, i)\u001b[0m\n\u001b[1;32m 464\u001b[0m return _get_objs_combined_axis(self.objs, axis=data_axis,\n\u001b[1;32m 465\u001b[0m \u001b[0mintersect\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mintersect\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 466\u001b[0;31m sort=self.sort)\n\u001b[0m\u001b[1;32m 467\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mIndexError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 468\u001b[0m \u001b[0mtypes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mtype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mobjs\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/indexes/api.py\u001b[0m in \u001b[0;36m_get_objs_combined_axis\u001b[0;34m(objs, intersect, axis, sort)\u001b[0m\n\u001b[1;32m 52\u001b[0m if hasattr(obj, '_get_axis')]\n\u001b[1;32m 53\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mobs_idxes\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 54\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_get_combined_index\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobs_idxes\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mintersect\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mintersect\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msort\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msort\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 55\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 56\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/indexes/api.py\u001b[0m in \u001b[0;36m_get_combined_index\u001b[0;34m(indexes, intersect, sort)\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0mindex\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mintersection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 69\u001b[0;31m \u001b[0mindex\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_union_indexes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindexes\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msort\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msort\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 70\u001b[0m \u001b[0mindex\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_ensure_index\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 71\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/indexes/api.py\u001b[0m in \u001b[0;36m_union_indexes\u001b[0;34m(indexes, sort)\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mother\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mindexes\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 107\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munion\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 108\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 109\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mkind\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'array'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36munion\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 2766\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrvals\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mvalue_set\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2767\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2768\u001b[0;31m \u001b[0mindexer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_indexer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2769\u001b[0m \u001b[0mindexer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mindexer\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnonzero\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2770\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/anaconda3/lib/python3.6/site-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_indexer\u001b[0;34m(self, target, method, limit, tolerance)\u001b[0m\n\u001b[1;32m 3227\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3228\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_unique\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3229\u001b[0;31m raise InvalidIndexError('Reindexing only valid with uniquely'\n\u001b[0m\u001b[1;32m 3230\u001b[0m ' valued Index objects')\n\u001b[1;32m 3231\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mInvalidIndexError\u001b[0m: Reindexing only valid with uniquely valued Index objects"
]
}
],
"source": [
"full2 = pd.concat([full, df3], axis=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Что-то пошло не так! На самом деле, проблема возникла ещё раньше: в `full` встречаются одинаковые номера строк, чего в датафреймах быть не должно (номер строки – её уникальный идентификатор). По-хорошему это нужно было поправить ещё при объединении двух датафреймов в один:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" A | \n",
" B | \n",
" C | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1 | \n",
" 2 | \n",
" 3 | \n",
"
\n",
" \n",
" | 1 | \n",
" 4 | \n",
" 7 | \n",
" 8 | \n",
"
\n",
" \n",
" | 2 | \n",
" 0 | \n",
" 8 | \n",
" 9 | \n",
"
\n",
" \n",
" | 3 | \n",
" 11 | \n",
" 12 | \n",
" 13 | \n",
"
\n",
" \n",
" | 4 | \n",
" 10 | \n",
" 18 | \n",
" 19 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" A B C\n",
"0 1 2 3\n",
"1 4 7 8\n",
"2 0 8 9\n",
"3 11 12 13\n",
"4 10 18 19"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# ignore_index - новые номера строк автоматом\n",
"full = pd.concat([df1, df2], ignore_index=True) \n",
"full # теперь все как надо, с 0 до 4"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"И дальше всё «склеивается» тоже без ошибок:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" A | \n",
" B | \n",
" C | \n",
" D | \n",
" E | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1 | \n",
" 2 | \n",
" 3 | \n",
" 22 | \n",
" 33 | \n",
"
\n",
" \n",
" | 1 | \n",
" 4 | \n",
" 7 | \n",
" 8 | \n",
" 44 | \n",
" 45 | \n",
"
\n",
" \n",
" | 2 | \n",
" 0 | \n",
" 8 | \n",
" 9 | \n",
" 46 | \n",
" 10 | \n",
"
\n",
" \n",
" | 3 | \n",
" 11 | \n",
" 12 | \n",
" 13 | \n",
" 11 | \n",
" 14 | \n",
"
\n",
" \n",
" | 4 | \n",
" 10 | \n",
" 18 | \n",
" 19 | \n",
" 18 | \n",
" 21 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" A B C D E\n",
"0 1 2 3 22 33\n",
"1 4 7 8 44 45\n",
"2 0 8 9 46 10\n",
"3 11 12 13 11 14\n",
"4 10 18 19 18 21"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"full2 = pd.concat([full, df3], axis=1)\n",
"full2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Однако часто мы сталкиваемся с более сложной задачей: объединить таблицы по определенному столбцу. Например, у нас есть таблица с разными показателями по регионам и таблица с рейтингами этих регионов. В обеих таблицах названия регионов указаны одинаково, но их порядок отличается. Воспользоваться обычным `concat()` по столбцам мы не сможем (технически всё сработает, но мы тогда перемешаем строки для разных регионов). Поэтому нам понадобится функция `merge()`. Для примера создадим два датафрейма с показателями по регионам:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"reg1 = pd.DataFrame([['Архангельская область', 56.0, 45.0, 67.0],\n",
" ['город Москва', 89.0, 32.5, 78.5],\n",
" ['Красноярский край', 88.0, 54.5, 34.5]],\n",
" columns=['region', 'index1', 'index2', 'index3'])\n",
"reg2 = pd.DataFrame([['Красноярский край', 5, 8],\n",
" ['Архангельская область', 7, 1],\n",
" ['город Москва', 2, 7]],\n",
" columns=['region', 'rating1', 'rating2'])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" region | \n",
" index1 | \n",
" index2 | \n",
" index3 | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Архангельская область | \n",
" 56.0 | \n",
" 45.0 | \n",
" 67.0 | \n",
"
\n",
" \n",
" | 1 | \n",
" город Москва | \n",
" 89.0 | \n",
" 32.5 | \n",
" 78.5 | \n",
"
\n",
" \n",
" | 2 | \n",
" Красноярский край | \n",
" 88.0 | \n",
" 54.5 | \n",
" 34.5 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" region index1 index2 index3\n",
"0 Архангельская область 56.0 45.0 67.0\n",
"1 город Москва 89.0 32.5 78.5\n",
"2 Красноярский край 88.0 54.5 34.5"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reg1"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" region | \n",
" rating1 | \n",
" rating2 | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Красноярский край | \n",
" 5 | \n",
" 8 | \n",
"
\n",
" \n",
" | 1 | \n",
" Архангельская область | \n",
" 7 | \n",
" 1 | \n",
"
\n",
" \n",
" | 2 | \n",
" город Москва | \n",
" 2 | \n",
" 7 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" region rating1 rating2\n",
"0 Красноярский край 5 8\n",
"1 Архангельская область 7 1\n",
"2 город Москва 2 7"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reg2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Теперь попробуем объединить их по столбцу `region`:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" region | \n",
" index1 | \n",
" index2 | \n",
" index3 | \n",
" rating1 | \n",
" rating2 | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Архангельская область | \n",
" 56.0 | \n",
" 45.0 | \n",
" 67.0 | \n",
" 7 | \n",
" 1 | \n",
"
\n",
" \n",
" | 1 | \n",
" город Москва | \n",
" 89.0 | \n",
" 32.5 | \n",
" 78.5 | \n",
" 2 | \n",
" 7 | \n",
"
\n",
" \n",
" | 2 | \n",
" Красноярский край | \n",
" 88.0 | \n",
" 54.5 | \n",
" 34.5 | \n",
" 5 | \n",
" 8 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" region index1 index2 index3 rating1 rating2\n",
"0 Архангельская область 56.0 45.0 67.0 7 1\n",
"1 город Москва 89.0 32.5 78.5 2 7\n",
"2 Красноярский край 88.0 54.5 34.5 5 8"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"regs = pd.merge(reg1, reg2, on='region') # on - столбец-идентификатор\n",
"regs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Всё получилось! У каждого региона указан его рейтинг, а не какой-то другой, разный порядок строк в датафреймах не помешал. Очевидно, что `merge()` будет работать корректно только в том случае, когда соответствия однозначны, то есть когда в столбце, по которому происходит объединение (назовём его столбец-идентификатор), нет повторяющихся значений. Здесь может возникнуть вопрос: а что будет, если какого-то значения в столбце-идентификаторе в одном из датафреймов не хватает? Например, во одном датафрейме три региона, а в другом – только два (по третьему информации нет, и строки с таким регионом тоже нет). Посмотрим. Для примера скопируем `reg2` и удалим из него Красноярский край."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" region | \n",
" rating1 | \n",
" rating2 | \n",
"
\n",
" \n",
" \n",
" \n",
" | 1 | \n",
" Архангельская область | \n",
" 7 | \n",
" 1 | \n",
"
\n",
" \n",
" | 2 | \n",
" город Москва | \n",
" 2 | \n",
" 7 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" region rating1 rating2\n",
"1 Архангельская область 7 1\n",
"2 город Москва 2 7"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reg3 = reg2.copy()\n",
"reg3 = reg3[reg3.region != 'Красноярский край']\n",
"reg3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Пытаемся объединить:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" region | \n",
" index1 | \n",
" index2 | \n",
" index3 | \n",
" rating1 | \n",
" rating2 | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Архангельская область | \n",
" 56.0 | \n",
" 45.0 | \n",
" 67.0 | \n",
" 7 | \n",
" 1 | \n",
"
\n",
" \n",
" | 1 | \n",
" город Москва | \n",
" 89.0 | \n",
" 32.5 | \n",
" 78.5 | \n",
" 2 | \n",
" 7 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" region index1 index2 index3 rating1 rating2\n",
"0 Архангельская область 56.0 45.0 67.0 7 1\n",
"1 город Москва 89.0 32.5 78.5 2 7"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pd.merge(reg1, reg3, on='region')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Информация по Красноярскому краю потерялась вообще, значения index1-index3, которые для этого региона были посчитаны, исчезли. Как это предотвратить? Объяснить Python, что нас интересует «расширенное» объединение, то есть склеивание датафреймов по всем ключам в датафрейме `reg1` и датафрейме `reg3`:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" region | \n",
" index1 | \n",
" index2 | \n",
" index3 | \n",
" rating1 | \n",
" rating2 | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Архангельская область | \n",
" 56.0 | \n",
" 45.0 | \n",
" 67.0 | \n",
" 7.0 | \n",
" 1.0 | \n",
"
\n",
" \n",
" | 1 | \n",
" город Москва | \n",
" 89.0 | \n",
" 32.5 | \n",
" 78.5 | \n",
" 2.0 | \n",
" 7.0 | \n",
"
\n",
" \n",
" | 2 | \n",
" Красноярский край | \n",
" 88.0 | \n",
" 54.5 | \n",
" 34.5 | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" region index1 index2 index3 rating1 rating2\n",
"0 Архангельская область 56.0 45.0 67.0 7.0 1.0\n",
"1 город Москва 89.0 32.5 78.5 2.0 7.0\n",
"2 Красноярский край 88.0 54.5 34.5 NaN NaN"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pd.merge(reg1, reg3, on='region', how='outer') # outer"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Теперь данные по Красноярскому краю не потеряны, просто на месте отсутствующих рейтингов стоят пропущенные значения `NaN`. Что такое этот `outer`? В теории, посвященной базам данных, существует два основных способа объединения: `inner` (внутренний) и `outer` (внешний). Метод `inner` означает склеивание баз данных по значениям, которые находятся в пересечении ключей этих баз, то есть по тем значениям столбца-идентификатора, которые являются общими для двух баз. Вспомним теорию множеств и применим её к нашим датафреймам выше.\n",
"\n",
"Множество регионов в `reg1`: $A = \\{\\text{Архангельская область, город Москва, Красноярский край}\\}$\n",
"\n",
"Множество регионов в `reg3`: $B = \\{\\text{Архангельская область, город Москва}\\}$\n",
"\n",
"Пересечение множеств: $A \\cap B = \\{\\text{Архангельская область, город Москва}\\}$\n",
"\n",
"Соответственно, те строки, которые относятся к регионам вне перечения множеств, отбрасываются. Метод `inner` используется в `merge()` по умолчанию, поэтому в примере выше мы сначала потеряли строку с Красноярским краем.\n",
"\n",
"Метод `outer` означает склеивание баз данных по значениям, которые находятся в объединении ключей этих баз, то есть по тем значениям, которые есть хотя бы в одном столбце-идентификаторе. Посмотрим на объединение множеств регионов в `reg1` и `reg3`:\n",
"\n",
"$A \\cup B = \\{\\text{Архангельская область, город Москва, Красноярский край}\\}$.\n",
"\n",
"Теперь ни один регион не будет потерян! И в случае, если какие-то ячейки в строке, соответствующей определенному региону, пустуют в одном из датафреймов, они просто будут заполнены пропущенными значениями (`NaN`), что мы и видели."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Методы `inner` и `outer` – далеко не единственные способы объединения датафреймов. При необходимости можно оставлять в таблице только те регионы, которые есть в первой базе (метод `left`) или во второй базе (метод `right`).\n",
"\n",
"В этом ноутбуке мы рассмотрели базовые случаи объединения датафреймов `pandas`. Конечно, это лишь малая часть возможностей этой библиотеки, но с остальными тонкостями объединения таблиц (случаи с одинаково названными столбцами, случаи частичного совпадения ключей и прочее), читателям предлагается познакомиться самостоятельно. \n",
"\n",
"* Документация по merge, join, concatenate: [ссылка](https://pandas.pydata.org/pandas-docs/stable/merging.html)\n",
"* Наглядный merge от Kaggle: [ссылка](https://www.kaggle.com/crawford/python-merge-tutorial)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}