{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python для анализа данных\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Работа с файлами: доступ и изменение" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Импортируем модуль `os` (от *operating system*), который позволяет работать с локальными (т.е. находящимися на компьютере) файлами и папками. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import os" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для начала определим рабочую папку – папку, которая используется по умолчанию (из неё по умолчанию запускается Jupyter, в неё сохраняются файлы и прочее, именно она отображается во вкладке *Home*)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'/Users/allat/Desktop'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.getcwd() # от get current working directory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На Windows в начале строки будет название диска (`C:` или `D:`), плюс, слэши могут быть обратными (`\\` или `\\\\`). В дальнейшем при создании папки или при указании пути к папке/файлу нужно помнить, что Python распознаёт только прямые слэши `/` (на Windows ещё двойные `\\\\`, но иногда бывают сбои)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Добавим пустую папку на рабочий стол (`Desktop`) и назовём её `to-test`:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "os.mkdir('/Users/allat/Desktop/to-test')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Совет:** если работаете с такого рода операциями впервые (никогда не создавали/удаляли файлы и папки через командную строку или подобные), лучше потестить всё в специально созданной папке или работать с копиями файлов. Python не будет уточнять, действительно ли вы хотите удалить файл или заменить файл новым с таким же названием, он молча удалит его или перезапишет. Будьте бдительны!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сделаем только что созданную папку рабочей:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "os.chdir('/Users/allat/Desktop/to-test') # chdir - от change directory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим на содержимое этой папки:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.listdir()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Пока пусто. Это ожидаемо. Исправим: создадим два пустых txt-файла:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "f1 = open('file1.txt', 'w')\n", "f1.close()\n", "f2 = open('file2.txt', 'w')\n", "f2.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Проверим теперь:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['file2.txt', 'file1.txt']" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.listdir() # появились!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функция `listdir()` возвращает список файлов/папок. По умолчанию, если ничего не указано в круглых скобках, \n", "возвращается содержимое текущей рабочей папки. Но в скобках можно указать путь к любой папке:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['misc',\n", " '.DS_Store',\n", " 'py-dat18',\n", " 'RprogData',\n", " 'CognTech',\n", " 'Py-programming-3',\n", " 'PyProg-2018',\n", " 'R-programming-3']" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.listdir('/Users/allat/Documents/github')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вернёмся к нашей папке и сохраним её содержимое одним списком:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "files = os.listdir()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['file2.txt', 'file1.txt']" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь проделаем следующее: будем открывать все файлы в списке `files`, записывать туда строку `\"hello\"` и закрывать. Понятно, что это игрушечный пример, но точно таким же образом можно будет сделать что-то посерьёзнее, например, открывать текстовые файлы, нормализовывать текст в них (приводить к нижнему регистру, убирать пунктуацию и лишние слова) и сохранять изменённый файл в другую папку. Нам понадобится цикл:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "for f in files:\n", " F = open(f, 'a')\n", " print('hello', file = F)\n", " F.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Проверим, что содержимое файлов изменилось:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['hello\\n']" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f = open('file1.txt', 'r') # и у файла file2.txt будет то же самое\n", "f.readlines()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На этом работу с модулем `os` можно закончить. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### JSON-файлы и таблицы `pandas`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "JSON расшифровывается как *JavaScript Object Notation*. Изначально этот формат хранения данных использовался в языке JavaScript, но теперь он потерял привязку к конкретному языку программирования и стал универсальным. С форматом JSON можно столкнуться при обращении к API, базам данных; формат часто применяется для хранения информации на сервере, к которому обращается сайт, например, в зависимости от запросов пользователей. *Object* здесь можно понимать как некоторую структуру хранения данных (список, кортеж, словарь), которая записывается в специальном виде, внешне напоминающим обычную строку. \n", "\n", "Импортируем модуль `json`:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "import json" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Начнём работать с реальными данными. Зайдём на [Портал](https://data.mos.ru/) открытых данных Правительства Москвы в раздел *Образование* и выберем набор данных *Перечень олимпиад школьников*. Кликнем *Экспорт* и скачаем файл в формате json. Скачается, правда, zip-архив, но его можно распаковать. Сохраним полный путь к json-файлу в переменную `name`:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "name = '/Users/allat/Desktop/data-6114-2018-12-10.json'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь загрузим содержимое файла в Python. Вообще при работе с json-файлами выделяют две операции: *десериализация* и *сериализация*. Десериализация – это преобразование объекта JSON в другую структуру данных (например, в питоновский словарь или список), а сериализация – это запись данных в формат JSON. Десериализуем: " ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "with open(name, \"r\", encoding=\"Windows-1251\") as read_file:\n", " data = json.load(read_file)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Запись с `with open() as read_file` эквивалентна созданию переменной `read_file` и присваиванию ей значения из `open()`. Плюс, так как текст в файле на кириллице, при загрузке файла имеет смысл указать кодировку (здесь это *Windows-1251*), иначе файл может не открыться или открыться, но с крокозябрами вместо букв.\n", "\n", "Посмотрим на `data`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В переменной `data` сохранён список словарей. Можем посмотреть на первый элемент списка:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'global_id': 39706536,\n", " 'ID': 43,\n", " 'Name': 'Всероссийская олимпиада по технологии',\n", " 'WebSite': [{'WebSite': 'vos.olimpiada.ru/2015/okrug'}],\n", " 'OlympiadType': 'Всероссийская олимпиада',\n", " 'Class': '7 - 11',\n", " 'Stage': 2,\n", " 'OlympiadDate': '05.12.2015',\n", " 'SchoolYear': 2015,\n", " 'Theme': 'Технология'}" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если привести данные в таком формате к привычному табличному виду, то получится, что в таблице у нас есть 8 столбцов (с *global_id* по *AdmArea*), а каждая строка таблицы описывается словарём как в ячейке выше. Данные в таком формате удобно хранить, к ним удобно писать запросы, выбирая значения по ключам в словарях, но иногда логичнее поместить их в таблицу. Для этого нам понадобится библиотека `pandas`:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "import pandas as pd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Превратим список словарей `data` в таблицу (датафрейм *pandas*):" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame(data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим на неё:" ] }, { "cell_type": "code", "execution_count": 20, "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", " \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", "
ClassIDNameOlympiadDateOlympiadTypeSchoolYearStageThemeWebSiteglobal_id
07 - 1143Всероссийская олимпиада по технологии05.12.2015Всероссийская олимпиада20152.0Технология[{'WebSite': 'vos.olimpiada.ru/2015/okrug'}]39706536
19 - 1144Всероссийская олимпиада по технологии01.02.2016, 02.02.2016Всероссийская олимпиада20163.0Технология[{'WebSite': 'vos.olimpiada.ru/2016/region'}]39706537
26 - 745Математический праздник21.02.2016Московская олимпиада2016NaNМатематика[{'WebSite': 'olympiads.mccme.ru/matprazdnik/'}]39706538
35 - 1146Московская астрономическая олимпиада06.02.2016Московская олимпиада2016NaNАстрономия[{'WebSite': 'astroolymp.ru/'}]39706540
45 - 1147Московская астрономическая олимпиада09.12.2015 - 18.01.2016Московская олимпиада2016NaNАстрономия[{'WebSite': 'astroolymp.ru/'}]39706541
\n", "
" ], "text/plain": [ " Class ID Name OlympiadDate \\\n", "0 7 - 11 43 Всероссийская олимпиада по технологии 05.12.2015 \n", "1 9 - 11 44 Всероссийская олимпиада по технологии 01.02.2016, 02.02.2016 \n", "2 6 - 7 45 Математический праздник 21.02.2016 \n", "3 5 - 11 46 Московская астрономическая олимпиада 06.02.2016 \n", "4 5 - 11 47 Московская астрономическая олимпиада 09.12.2015 - 18.01.2016 \n", "\n", " OlympiadType SchoolYear Stage Theme \\\n", "0 Всероссийская олимпиада 2015 2.0 Технология \n", "1 Всероссийская олимпиада 2016 3.0 Технология \n", "2 Московская олимпиада 2016 NaN Математика \n", "3 Московская олимпиада 2016 NaN Астрономия \n", "4 Московская олимпиада 2016 NaN Астрономия \n", "\n", " WebSite global_id \n", "0 [{'WebSite': 'vos.olimpiada.ru/2015/okrug'}] 39706536 \n", "1 [{'WebSite': 'vos.olimpiada.ru/2016/region'}] 39706537 \n", "2 [{'WebSite': 'olympiads.mccme.ru/matprazdnik/'}] 39706538 \n", "3 [{'WebSite': 'astroolymp.ru/'}] 39706540 \n", "4 [{'WebSite': 'astroolymp.ru/'}] 39706541 " ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.head() # head - первые несколько строк" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для примера можем экспортировать таблицу в файл Excel:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "writer = pd.ExcelWriter('olymp.xlsx')\n", "df.to_excel(writer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь вернёмся к json-файлам и посмотрим на сериализацию, конвертацию в JSON. Создадим словарь `d` с данными по пользователям:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "d = {'user1': {'name' : 'Ann', 'age': 25},\n", " 'user2' : {'name' : 'Bill', 'age' : 28}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Запишем содержимое в json-файл, используя функцию `dump`:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "with open(\"data_file.json\", \"w\") as write_file:\n", " json.dump(d, write_file)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можно найти файл на компьютере, открыть его с помощью блокнота и убедиться, что всё действительно сохранилось. \n", "\n", "В модуле `json` есть пары связанных функций: `dump()` и `dumps()`, `load()` и `loads()`. Функции без окончания `s` работают с файлами, а с `-s` – со строками. Сохраним `d` как строку:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'{\"user1\": {\"name\": \"Ann\", \"age\": 25}, \"user2\": {\"name\": \"Bill\", \"age\": 28}}'" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "json_string = json.dumps(d)\n", "json_string" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В завершение обсуждения json-файлов хочется отметить, что в JSON нет понятия, аналогичного кортежам в Python. Это означает, что при записи питоновского объекта в json-файл кортежи преобразуются в списки. Пример:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "my_tuple = ('Ann', 23)\n", "tuple_dec = json.dumps(my_tuple) # сохраняем как json-строку\n", "tuple_enc = json.loads(tuple_dec) # считываем json из строки" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('Ann', 23)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_tuple # было" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Ann', 23]" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tuple_enc # стало" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Всё!" ] } ], "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.8" } }, "nbformat": 4, "nbformat_minor": 2 }