{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# pd.Series\n", "\n", "## Создание \n", "\n", "Создать `pd.Series` можно:\n", "1. Из скаляра (число, строка); \n", "2. Из итерируемого объекта (`list`, `np.array` и т.д.);\n", "3. Из словаря.\n", "\n", "\n", "Создание `pd.Series` из скаляра создаёт столбец с одним элементом. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 abc\n", "dtype: object" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "import numpy as np\n", "\n", "s = pd.Series(\"abc\")\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вывод сообщает, что `s` --- столбец из одного элемента `\"abc\"` типа `object`, индекс которого равен `0`. \n", "- `object` --- самый общий тип данных в столбце. В `pd.Series` типа `object` можно хранить данные любого типа, но этого следует по возможности избегать из соображений производительности, читабельности и удобства применений методов библиотеки `pandas`. Указать тип столбца можно дополнительным параметром `dtype` (\"string\" для строк).\n", "- при создании `pd.Series` скаляра, индекс единственного элемента будет равен 0. Можно явно указать индекс с помощью параметра `index`. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42 abc\n", "dtype: string" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = pd.Series(\"abc\", dtype=\"string\", index=[42])\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Создание столбцов из одного элемента применяется не очень часто, хотя и встречается. Более содержательный пример --- создание `pd.Series` из списка или массива `NumPy`. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 7\n", "1 12\n", "2 42\n", "dtype: int64" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data = [7, 12, 42]\n", "s = pd.Series(data)\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Создан столбец с элементами из списка `data` с такими индексами, какие они были в исходном списке. \n", "\n", "У объекта `pd.Series` можно спросить значения аттрибутом `values` (возвращается `np.array`)\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 7, 12, 42], dtype=int64)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "а индекс соответствующим атрибутом `index`" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "RangeIndex(start=0, stop=3, step=1)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.index" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если индекс не указывается явно, то создаётся [RangeIndex](https://pandas.pydata.org/docs/reference/api/pandas.RangeIndex.html), т.е. как бы смещение от начала массива. Как и в случае скаляра, можно было бы явно указать индекс при создании столбца.\n", "\n", "При создании `pd.Series` из словаря, ключи попадают в индекс столбца, а значения попадают в данные столбца." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "a 7\n", "b 12\n", "c 42\n", "dtype: int64" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d = {\n", " \"a\": 7,\n", " \"b\": 12,\n", " \"c\": 42\n", "}\n", "s = pd.Series(d)\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Индексация \n", "\n", "Получать значение из столбца можно двумя способами:\n", "- по метке (используя индекс столбца);\n", "- по смещению.\n", "\n", "```{warning}\n", "Оператор `[]` по умолчанию возвращает значение по метке, но если индекс столбца не целочисленного типа, а значение между скобками --- целое число, то индексирует по смещению. Т.е. есть некоторая неоднозначность в таком способе индексации, что может привести к ошибкам. Лучшей практикой считается применение методов [pd.Series.loc](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.loc.html#pandas.DataFrame.loc) и [pd.Series.iloc](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.iloc.html), чтобы явно указать каким способом следует осуществлять индексацию.\n", "```\n", "\n", "Выражение `s.loc[index]` возвращает элемент(ы) столбца с индексом `index`, а метод `s.iloc[i]` возвращает `i`-й элемент столбца по смещению от начала (как оператор `[]` списков и массивов `NumPy`). Продемонстрируем это на примере столбца с индексом из символов." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a 0\n", "b 1\n", "c 2\n", "d 3\n", "e 4\n", "f 5\n", "g 6\n", "dtype: int64\n", "loc[\"a\"]: 1, iloc[1]: 1\n" ] } ], "source": [ "symbols = \"abcdefg\"\n", "s = pd.Series(data=range(len(symbols)), index=list(symbols))\n", "print(s)\n", "\n", "print(f'loc[\"a\"]: {s.loc[\"b\"]}, iloc[1]: {s.iloc[1]}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Т.е. до одного и того же элемента серии можно достучаться разными способами. Индексация по смещению (`iloc`) аналогична `NumPy` (в том числе и срезы). Индексация по индексу (`loc`) больше похожа на получение значения по ключу в словаре. Можно осуществлять срезы и по индексу, но в данном случае правая граница попадает в срез. " ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "b 1\n", "c 2\n", "d 3\n", "e 4\n", "f 5\n", "dtype: int64" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s[\"b\":\"f\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{warning}\n", "В одном столбце у разных элементов могут быть одинаковые метки (дубликаты). Метод `loc` возвратит все элементы с такой меткой, но срезы в таком случае могут сломаться из-за неоднозначности определения границ среза. \n", "```\n", "\n", "Обоими методами можно получить сразу несколько элементов `pd.Series`, передав в них список смещений/меток." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a 1\n", "d 1\n", "e 1\n", "dtype: int64\n", "a 1\n", "d 1\n", "e 1\n", "dtype: int64\n" ] } ], "source": [ "print(s.loc[[\"a\", \"d\", \"e\"]])\n", "print(s.iloc[[0, 3, 4]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Индексация по смещению работает чуть быстрее, чем индексация по меткам, но последняя предоставляет более удобный функционал. Позже будет раскрыто, что в таблицах (`DataFrame`) эти же метки используются для индексации по строкам. Это позволяет:\n", "1. осуществлять быстрый поиск строк таблицы по метке. Это может значительно ускорить программы, постоянно осуществляющие такие запросы по значению какого-то конкретного столбца (или пары столбцов, `pandas` допускает [MultiIndex](https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html)), т.к. такая индексация по меткам гораздо быстрее, чем линейный (и даже бинарный) поиск по данным. Однако если в индексе много дубликатов, то эффективность такого способа индексации уменьшается;\n", "2. Выравнивать данные разных столбцов между собой. Это может пригодиться, если данные приходят из разных источников и упорядоченны по-разному, но все из них помечены однообразно. В таком случае можно не затрудняясь выравнять эти данные между собой средствами `pandas`.\n", "\n", "\n", "## Изменяемость pd.Series\n", "\n", "Аналогично с массивами `NumPy`, можно изменять содержимое ячейки/среза, но нельзя изменять размер (длину) столбца. \n", "\n", "Например, изменить содержимое ячейки по метке `a` можно сделать следующим образом." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "a 42\n", "b 1\n", "c 2\n", "d 3\n", "e 4\n", "f 5\n", "g 6\n", "dtype: int64" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.loc['a'] = 42\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "С изменением размера следует быть аккуратным. Следующая операция как бы увеличит размер столбца `s`. Но `pandas` перевыделяет память, копирует данные старого столбца и дозаписывает новое значение при каждом добавлении нового значения в столбец или новой строки в таблицу. Т.е. использовать объекты `pandas` для накопления строк по одной крайне неэффективно. Если есть такая необходимость, то обычно данные накапливают в контейнерах `python` (список, словарь) и трансформируют в `pandas` объект в самом конце или по накоплении некого блока информации. Альтернативой служит создание большой таблицы/столбца и работа только с первыми `n` строками, увеличивая `n` при необходимости. " ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "a 42\n", "b 1\n", "c 2\n", "d 3\n", "e 4\n", "f 5\n", "g 6\n", "z 42\n", "dtype: int64" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.loc['z'] = 42\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Операции над pd.Series\n", "\n", "При выполнении операций над столбцами метки играют важную роль. Например, при сложении двух столбцов складываются значения с одинаковыми метками. Индекс результирующего столбца --- объединение индексов столбцов слагаемых, а напротив тех меток, которые присутствуют в индексе только одного столбца записывается значение `np.nan`. " ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "a NaN\n", "b 22.0\n", "c 13.0\n", "d NaN\n", "dtype: float64" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s1 = pd.Series([1, 2, 3], index=[\"a\", \"b\", \"c\"], dtype=\"int\")\n", "s2 = pd.Series([10, 20, 30], index=[\"c\", \"b\", \"d\"], dtype=\"int\")\n", "s = s1 + s2\n", "s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Рисунок ниже иллюстрирует, что произошло. \n", "\n", "```{figure} /_static/lecture_specific/pandas/addition.png\n", ":scale: 30%\n", "```\n", "\n", "Также можно заметить, что данные результирующего столбца имеют тип `float64`, несмотря на то, что исходные столбцы целочисленного типа. Это объясняется тем, что появившееся значения `np.nan` имеют такой тип.\n", "\n", "Большинство методов массивов `NumPy` переопределены в `pandas` для `pd.Series` таким образом, чтобы обрабатывать пропущенные значения, которые моделируются с помощью `np.nan`. " ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "17.5" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.mean()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все логические операции работают аналогично `NumPy` (сравнение `NaN` с чем угодно всегда `False`) и также допускается логическая индексация (в случае булевой маски `mask`, `.loc[mask]` и `.iloc[mask]` делают одно и тоже, поэтому тут оправданно применение простых квадратных скобок `[]`)." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "b 22.0\n", "c 13.0\n", "dtype: float64" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s[s > 10]" ] } ], "metadata": { "interpreter": { "hash": "cd49b4596bae9c980ff74fdf93e8fe80e447435ae307c062fad6c4f9ef2eb47f" }, "kernelspec": { "display_name": "Python 3.8.10 64-bit ('venv': venv)", "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.8.10" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }