{ "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ABC
0123
1478
2089
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ABC
0111213
1101819
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ABC
0123
1478
2089
0111213
1101819
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
DE
02233
14445
24610
31114
41821
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ABC
0123
1478
2089
3111213
4101819
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ABCDE
01232233
14784445
20894610
31112131114
41018191821
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
regionindex1index2index3
0Архангельская область56.045.067.0
1город Москва89.032.578.5
2Красноярский край88.054.534.5
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
regionrating1rating2
0Красноярский край58
1Архангельская область71
2город Москва27
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
regionindex1index2index3rating1rating2
0Архангельская область56.045.067.071
1город Москва89.032.578.527
2Красноярский край88.054.534.558
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
regionrating1rating2
1Архангельская область71
2город Москва27
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
regionindex1index2index3rating1rating2
0Архангельская область56.045.067.071
1город Москва89.032.578.527
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
regionindex1index2index3rating1rating2
0Архангельская область56.045.067.07.01.0
1город Москва89.032.578.52.07.0
2Красноярский край88.054.534.5NaNNaN
\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 }