{ "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", " | Class | \n", "ID | \n", "Name | \n", "OlympiadDate | \n", "OlympiadType | \n", "SchoolYear | \n", "Stage | \n", "Theme | \n", "WebSite | \n", "global_id | \n", "
---|---|---|---|---|---|---|---|---|---|---|
0 | \n", "7 - 11 | \n", "43 | \n", "Всероссийская олимпиада по технологии | \n", "05.12.2015 | \n", "Всероссийская олимпиада | \n", "2015 | \n", "2.0 | \n", "Технология | \n", "[{'WebSite': 'vos.olimpiada.ru/2015/okrug'}] | \n", "39706536 | \n", "
1 | \n", "9 - 11 | \n", "44 | \n", "Всероссийская олимпиада по технологии | \n", "01.02.2016, 02.02.2016 | \n", "Всероссийская олимпиада | \n", "2016 | \n", "3.0 | \n", "Технология | \n", "[{'WebSite': 'vos.olimpiada.ru/2016/region'}] | \n", "39706537 | \n", "
2 | \n", "6 - 7 | \n", "45 | \n", "Математический праздник | \n", "21.02.2016 | \n", "Московская олимпиада | \n", "2016 | \n", "NaN | \n", "Математика | \n", "[{'WebSite': 'olympiads.mccme.ru/matprazdnik/'}] | \n", "39706538 | \n", "
3 | \n", "5 - 11 | \n", "46 | \n", "Московская астрономическая олимпиада | \n", "06.02.2016 | \n", "Московская олимпиада | \n", "2016 | \n", "NaN | \n", "Астрономия | \n", "[{'WebSite': 'astroolymp.ru/'}] | \n", "39706540 | \n", "
4 | \n", "5 - 11 | \n", "47 | \n", "Московская астрономическая олимпиада | \n", "09.12.2015 - 18.01.2016 | \n", "Московская олимпиада | \n", "2016 | \n", "NaN | \n", "Астрономия | \n", "[{'WebSite': 'astroolymp.ru/'}] | \n", "39706541 | \n", "