Различные задачи по анализу данных

Intermediate

This tutorial is from open-source community. Access the source code

Введение

В этом практическом занятии (лабораторной работе) вы научитесь работать с различными контейнерами данных в Python и использовать генераторы списков, множеств и словарей. Вы также изучите модуль collections, который предоставляет полезные инструменты для обработки данных.

Python предлагает мощные инструменты для манипуляции и анализа данных. В этом практическом занятии вы потренируетесь использовать встроенные структуры данных Python и специальные инструменты для анализа различных наборов данных. Начиная с простого набора данных о портфеле, вы перейдете к анализу данных о автобусах Чикагской транспортной администрации (Chicago Transit Authority) для извлечения значимой информации.

Это Guided Lab, который предоставляет пошаговые инструкции, чтобы помочь вам учиться и практиковаться. Внимательно следуйте инструкциям, чтобы выполнить каждый шаг и получить практический опыт. Исторические данные показывают, что это лабораторная работа уровня начальный с процентом завершения 82%. Он получил 96% положительных отзывов от учащихся.

Работа со словарями и данными в формате CSV

Начнем с изучения простого набора данных о акциях. На этом этапе вы научитесь считывать данные из файла в формате CSV и хранить их в структурированном формате с использованием словарей.

Файл CSV (Comma-Separated Values, разделенные запятыми значения) представляет собой распространенный способ хранения табличных данных, где каждая строка представляет строку таблицы, а значения разделены запятыми. Словари в Python - это мощная структура данных, которая позволяет хранить пары ключ - значение. Используя словари, мы можем организовать данные из файла CSV более осмысленным образом.

Сначала создайте новый файл Python в WebIDE, следуя этим шагам:

  1. Нажмите на кнопку "New File" в WebIDE.
  2. Назовите файл readport.py.
  3. Скопируйте и вставьте следующий код в файл:
## readport.py

import csv

## A function that reads a file into a list of dictionaries
def read_portfolio(filename):
    portfolio = []
    with open(filename) as f:
        rows = csv.reader(f)
        headers = next(rows)   ## Skip the header row
        for row in rows:
            record = {
                'name': row[0],
                'shares': int(row[1]),
                'price': float(row[2])
            }
            portfolio.append(record)
    return portfolio

Этот код определяет функцию read_portfolio, которая выполняет несколько важных задач:

  1. Он открывает файл CSV, указанный параметром filename. Функция open используется для доступа к файлу, а оператор with гарантирует, что файл будет правильно закрыт после завершения чтения.
  2. Он пропускает строку заголовка. Строка заголовка обычно содержит имена столбцов в файле CSV. Мы используем next(rows), чтобы переместить итератор на следующую строку, тем самым пропуская заголовок.
  3. Для каждой строки данных он создает словарь. Ключами словаря являются 'name', 'shares' и 'price'. Эти ключи помогут нам более интуитивно доступать к данным.
  4. Он преобразует количество акций в целые числа и цены в числа с плавающей запятой. Это важно, потому что данные, прочитанные из файла CSV, изначально имеют строковый формат, а для вычислений нам нужны числовые значения.
  5. Он добавляет каждый словарь в список с именем portfolio. Этот список будет содержать все записи из файла CSV.
  6. Наконец, он возвращает полный список словарей.

Теперь создадим файл для данных о транспорте. Создайте новый файл с именем readrides.py со следующим содержимым:

## readrides.py

import csv

def read_rides_as_dicts(filename):
    """
    Read the CTA bus data as a list of dictionaries
    """
    records = []
    with open(filename) as f:
        rows = csv.reader(f)
        headers = next(rows)   ## Skip header
        for row in rows:
            route = row[0]
            date = row[1]
            daytype = row[2]
            rides = int(row[3])
            record = {
                'route': route,
                'date': date,
                'daytype': daytype,
                'rides': rides
            }
            records.append(record)
    return records

Функция read_rides_as_dicts работает аналогично функции read_portfolio. Она считывает файл CSV, связанный с данными о автобусах Чикагской транспортной администрации (CTA), пропускает строку заголовка, создает словарь для каждой строки данных и сохраняет эти словари в списке.

Теперь протестируем функцию read_portfolio, открыв терминал в WebIDE:

  1. Нажмите на меню "Terminal" и выберите "New Terminal".
  2. Запустите интерпретатор Python, введя python3.
  3. Выполните следующие команды:
>>> from readport import read_portfolio
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')
>>> from pprint import pprint
>>> pprint(portfolio)
[{'name': 'AA', 'price': 32.2, 'shares': 100},
 {'name': 'IBM', 'price': 91.1, 'shares': 50},
 {'name': 'CAT', 'price': 83.44, 'shares': 150},
 {'name': 'MSFT', 'price': 51.23, 'shares': 200},
 {'name': 'GE', 'price': 40.37, 'shares': 95},
 {'name': 'MSFT', 'price': 65.1, 'shares': 50},
 {'name': 'IBM', 'price': 70.44, 'shares': 100}]

Функция pprint (красивый вывод) используется здесь для отображения данных в более читаемом формате. Каждый элемент в списке представляет собой словарь, описывающий одну позицию в портфеле акций. Словарь имеет следующие ключи:

  • Символ акции (name): Это аббревиатура, используемая для идентификации акции.
  • Количество принадлежащих акций (shares): Это показывает, сколько акций данной компании находится в портфеле.
  • Цена покупки за одну акцию (price): Это цена, по которой каждая акция была куплена.

Обратите внимание, что некоторые акции, такие как 'MSFT' и 'IBM', встречаются несколько раз. Они представляют разные покупки одной и той же акции, которые могли быть совершены в разное время и по разным ценам.

Использование генераторов списков, множеств и словарей

Генераторы в Python - это очень полезный и компактный способ создания новых коллекций на основе существующих. Коллекции в Python могут быть списками, множествами или словарями, которые являются контейнерами для различных типов данных. Генераторы позволяют фильтровать определенные данные, преобразовывать их определенным образом и организовывать более эффективно. В этом разделе мы используем наши данные о портфеле акций, чтобы понять, как работают эти генераторы.

Сначала вам нужно открыть терминал Python, как вы делали на предыдущем этапе. После открытия терминала вы будете поочередно вводить следующие примеры. Такой практический подход поможет вам понять, как работают генераторы на практике.

Генераторы списков

Генератор списка - это специальный синтаксис в Python, который создает новый список. Он делает это, применяя выражение к каждому элементу существующей коллекции.

Начнем с примера. Сначала мы импортируем функцию для чтения данных о портфеле. Затем мы используем генератор списка, чтобы отфильтровать определенные позиции в портфеле.

>>> from readport import read_portfolio
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')

## Find all holdings with more than 100 shares
>>> large_holdings = [s for s in portfolio if s['shares'] > 100]
>>> print(large_holdings)
[{'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}]

В этом коде мы сначала импортируем функцию read_portfolio и используем ее для чтения данных о портфеле из файла CSV. Затем генератор списка [s for s in portfolio if s['shares'] > 100] проходит по каждому элементу s в коллекции portfolio. Он включает элемент s в новый список large_holdings только в том случае, если количество акций в этой позиции больше 100.

Генераторы списков также можно использовать для выполнения вычислений. Вот несколько примеров:

## Calculate the total cost of each holding (shares * price)
>>> holding_costs = [s['shares'] * s['price'] for s in portfolio]
>>> print(holding_costs)
[3220.0, 4555.0, 12516.0, 10246.0, 3835.15, 3255.0, 7044.0]

## Calculate the total cost of the entire portfolio
>>> total_portfolio_cost = sum([s['shares'] * s['price'] for s in portfolio])
>>> print(total_portfolio_cost)
44671.15

В первом примере генератор списка [s['shares'] * s['price'] for s in portfolio] вычисляет общую стоимость каждой позиции в портфеле, умножая количество акций на цену для каждого элемента в portfolio. Во втором примере мы используем функцию sum вместе с генератором списка, чтобы вычислить общую стоимость всего портфеля.

Генераторы множеств

Генератор множества используется для создания множества на основе существующей коллекции. Множество - это коллекция, которая содержит только уникальные значения.

Давайте посмотрим, как это работает с нашими данными о портфеле:

## Find all unique stock names
>>> unique_stocks = {s['name'] for s in portfolio}
>>> print(unique_stocks)
{'MSFT', 'IBM', 'AA', 'GE', 'CAT'}

В этом коде генератор множества {s['name'] for s in portfolio} проходит по каждому элементу s в portfolio и добавляет название акции (s['name']) в множество unique_stocks. Поскольку множества хранят только уникальные значения, в результате у нас получается список всех различных акций в нашем портфеле без дубликатов.

Генераторы словарей

Генератор словаря создает новый словарь, применяя выражения для создания пар ключ - значение.

Вот пример использования генератора словаря для подсчета общего количества акций каждой компании в нашем портфеле:

## Create a dictionary to count total shares for each stock
>>> totals = {s['name']: 0 for s in portfolio}
>>> for s in portfolio:
...     totals[s['name']] += s['shares']
...
>>> print(totals)
{'AA': 100, 'IBM': 150, 'CAT': 150, 'MSFT': 250, 'GE': 95}

В первой строке генератор словаря {s['name']: 0 for s in portfolio} создает словарь, в котором каждое название акции (s['name']) является ключом, а начальное значение для каждого ключа равно 0. Затем мы используем цикл for, чтобы пройти по каждому элементу в portfolio. Для каждого элемента мы добавляем количество акций (s['shares']) к соответствующему значению в словаре totals.

Эти генераторы очень мощные, потому что позволяют преобразовывать и анализировать данные всего несколькими строками кода. Они являются отличным инструментом в вашем арсенале Python - программирования.

Исследование модуля collections

В Python встроенные контейнеры, такие как списки, словари и множества, очень полезны. Однако модуль collections Python позволяет пойти дальше, предоставляя специализированные типы контейнеров, которые расширяют функциональность этих встроенных контейнеров. Давайте более подробно рассмотрим некоторые из этих полезных типов данных.

Вы будете продолжать работать в своем Python - терминале и следовать примерам ниже.

Counter

Класс Counter является подклассом словаря. Его основная цель - подсчитывать хэшируемые объекты. Он предоставляет удобный способ подсчета элементов и поддерживает различные операции.

Сначала нам нужно импортировать класс Counter и функцию для чтения портфеля. Затем мы прочитаем портфель из файла CSV.

>>> from collections import Counter
>>> from readport import read_portfolio
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')

Теперь мы создадим объект Counter для подсчета количества акций каждой компании по ее названию.

## Create a counter to count shares by stock name
>>> totals = Counter()
>>> for s in portfolio:
...     totals[s['name']] += s['shares']
...
>>> print(totals)
Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})

Одной из замечательных особенностей объекта Counter является то, что он автоматически инициализирует новые ключи со значением подсчета, равным 0. Это означает, что вам не нужно проверять, существует ли ключ, прежде чем увеличить его счетчик, что упрощает код для накопления подсчетов.

У счетчиков также есть специальные методы. Например, метод most_common() очень полезен для анализа данных.

## Get the two stocks with the most shares
>>> most_common_stocks = totals.most_common(2)
>>> print(most_common_stocks)
[('MSFT', 250), ('IBM', 150)]

Кроме того, счетчики можно комбинировать с помощью арифметических операций.

## Create another counter
>>> more = Counter()
>>> more['IBM'] = 75
>>> more['AA'] = 200
>>> more['ACME'] = 30
>>> print(more)
Counter({'AA': 200, 'IBM': 75, 'ACME': 30})

## Add two counters together
>>> combined = totals + more
>>> print(combined)
Counter({'AA': 300, 'MSFT': 250, 'IBM': 225, 'CAT': 150, 'GE': 95, 'ACME': 30})

defaultdict

defaultdict похож на обычный словарь, но имеет уникальную особенность. Он предоставляет значение по умолчанию для ключей, которые еще не существуют. Это может упростить ваш код, так как вам больше не нужно проверять, существует ли ключ, прежде чем использовать его.

>>> from collections import defaultdict

## Group portfolio entries by stock name
>>> byname = defaultdict(list)
>>> for s in portfolio:
...     byname[s['name']].append(s)
...
>>> print(byname['IBM'])
[{'name': 'IBM', 'shares': 50, 'price': 91.1}, {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>> print(byname['AA'])
[{'name': 'AA', 'shares': 100, 'price': 32.2}]

Когда вы создаете defaultdict(list), он автоматически создает новый пустой список для каждого нового ключа. Таким образом, вы можете напрямую добавлять элементы в значение ключа, даже если ключ до этого не существовал. Это устраняет необходимость проверять, существует ли ключ, и создавать пустой список вручную.

Вы также можете использовать другие функции - фабрики по умолчанию. Например, вы можете использовать int, float или даже свою собственную пользовательскую функцию.

## Use defaultdict with int to count items
>>> word_counts = defaultdict(int)
>>> words = ['apple', 'orange', 'banana', 'apple', 'orange', 'apple']
>>> for word in words:
...     word_counts[word] += 1
...
>>> print(word_counts)
defaultdict(<class 'int'>, {'apple': 3, 'orange': 2, 'banana': 1})

Эти специализированные типы контейнеров из модуля collections могут сделать ваш код более компактным и эффективным при работе с данными.

Задача по анализу данных с использованием данных Чикагской транспортной администрации

Теперь, когда вы потренировались работать с различными структурами данных Python и модулем collections, пришло время применить эти навыки в реальной задаче по анализу данных. В этом эксперименте мы будем анализировать данные о пассажиропотоке на автобусах Чикагской транспортной администрации (CTA). Эта практическая работа поможет вам понять, как использовать Python для извлечения значимой информации из реальных наборов данных.

Понимание данных

Сначала давайте посмотрим на транспортные данные, с которыми мы будем работать. В вашем Python - терминале вы запустите некоторый код, чтобы загрузить данные и понять их базовую структуру.

>>> import readrides
>>> rows = readrides.read_rides_as_dicts('/home/labex/project/ctabus.csv')
>>> print(len(rows))
## This will show the number of records in the dataset

>>> ## Let's look at the first record to understand the structure
>>> import pprint
>>> pprint.pprint(rows[0])

Инструкция import readrides импортирует пользовательский модуль, который содержит функцию для чтения данных из файла CSV. Функция readrides.read_rides_as_dicts читает данные из указанного файла CSV и преобразует каждую строку в словарь. len(rows) дает нам общее количество записей в наборе данных. Выводя первую запись с помощью pprint.pprint(rows[0]), мы можем четко увидеть структуру каждой записи.

Данные содержат ежедневные записи о пассажиропотоке на различных автобусных маршрутах. Каждая запись включает:

  • route: Номер автобусного маршрута
  • date: Дата в формате "YYYY - MM - DD"
  • daytype: "W" для буднего дня, "A" для субботы или "U" для воскресенья/праздника
  • rides: Количество пассажиров в этот день

Задачи анализа

Давайте по очереди решим каждую из задачи:

Вопрос 1: Сколько автобусных маршрутов существует в Чикаго?

Чтобы ответить на этот вопрос, нам нужно найти все уникальные номера маршрутов в наборе данных. Мы будем использовать генератор множества для этой задачи.

>>> ## Get all unique route numbers using a set comprehension
>>> unique_routes = {row['route'] for row in rows}
>>> print(len(unique_routes))

Генератор множества - это компактный способ создания множества. В этом случае мы проходим по каждой строке в списке rows и извлекаем значение route. Поскольку множество хранит только уникальные элементы, в результате у нас получается множество всех уникальных номеров маршрутов. Вывод длины этого множества дает нам общее количество уникальных автобусных маршрутов.

Мы также можем посмотреть, какие это маршруты:

>>> ## Print a few of the route numbers
>>> print(list(unique_routes)[:10])

Здесь мы преобразуем множество уникальных маршрутов в список и выводим первые 10 элементов этого списка.

Вопрос 2: Сколько человек воспользовалось автобусом №22 2 февраля 2011 года?

Для этого вопроса нам нужно отфильтровать данные, чтобы найти конкретную запись, которая соответствует заданному маршруту и дате.

>>> ## Find rides on route 22 on February 2, 2011
>>> target_date = "2011-02-02"
>>> target_route = "22"
>>>
>>> for row in rows:
...     if row['route'] == target_route and row['date'] == target_date:
...         print(f"Rides on route {target_route} on {target_date}: {row['rides']}")
...         break

Сначала мы определяем переменные target_date и target_route. Затем мы проходим по каждой строке в списке rows. Для каждой строки мы проверяем, совпадают ли route и date с нашими целевыми значениями. Если совпадение найдено, мы выводим количество пассажиров и выходим из цикла, так как мы нашли запись, которую искали.

Вы можете изменить эти переменные, чтобы проверить любой маршрут в любую дату.

Вопрос 3: Каково общее количество поездок на каждом автобусном маршруте?

Давайте используем Counter для подсчета общего количества поездок на каждом маршруте. Counter - это подкласс словаря из модуля collections, который используется для подсчета хэшируемых объектов.

>>> from collections import Counter
>>>
>>> ## Initialize a counter
>>> total_rides_by_route = Counter()
>>>
>>> ## Sum up rides for each route
>>> for row in rows:
...     total_rides_by_route[row['route']] += row['rides']
...
>>> ## View the top 5 routes by total ridership
>>> for route, rides in total_rides_by_route.most_common(5):
...     print(f"Route {route}: {rides:,} total rides")

Сначала мы импортируем класс Counter из модуля collections. Затем мы инициализируем пустой счетчик с именем total_rides_by_route. По мере прохождения по каждой строке в списке rows мы добавляем количество поездок на каждом маршруте в счетчик. Наконец, мы используем метод most_common(5), чтобы получить топ - 5 маршрутов с наибольшим общим пассажиропотоком, и выводим результаты.

Вопрос 4: Какие пять автобусных маршрутов имели наибольший десятилетний рост пассажиропотока с 2001 по 2011 год?

Это более сложная задача. Мы должны сравнить пассажиропоток в 2001 и 2011 годах для каждого маршрута.

>>> ## Create dictionaries to store total annual rides by route
>>> rides_2001 = Counter()
>>> rides_2011 = Counter()
>>>
>>> ## Collect data for each year
>>> for row in rows:
...     if row['date'].startswith('2001-'):
...         rides_2001[row['route']] += row['rides']
...     elif row['date'].startswith('2011-'):
...         rides_2011[row['route']] += row['rides']
...
>>> ## Calculate increases
>>> increases = {}
>>> for route in unique_routes:
...     if route in rides_2001 and route in rides_2011:
...         increase = rides_2011[route] - rides_2001[route]
...         increases[route] = increase
...
>>> ## Find the top 5 routes with the biggest increases
>>> import heapq
>>> top_5_increases = heapq.nlargest(5, increases.items(), key=lambda x: x[1])
>>>
>>> ## Display the results
>>> print("Top 5 routes with the greatest ridership increase from 2001 to 2011:")
>>> for route, increase in top_5_increases:
...     print(f"Route {route}: increased by {increase:,} rides")
...     print(f"  2001 rides: {rides_2001[route]:,}")
...     print(f"  2011 rides: {rides_2011[route]:,}")
...     print()

Сначала мы создаем два объекта Counter, rides_2001 и rides_2011, чтобы хранить общий пассажиропоток для каждого маршрута в 2001 и 2011 годах соответственно. По мере прохождения по каждой строке в списке rows мы проверяем, начинается ли дата с '2001 -' или '2011 -', и добавляем количество поездок в соответствующий счетчик.

Затем мы создаем пустой словарь increases, чтобы хранить рост пассажиропотока для каждого маршрута. Мы проходим по уникальным маршрутам и вычисляем рост, вычитая количество поездок в 2001 году из количества поездок в 2011 году для каждого маршрута.

Чтобы найти топ - 5 маршрутов с наибольшим ростом, мы используем функцию heapq.nlargest. Эта функция принимает количество элементов для возврата (в данном случае 5), итерируемый объект (increases.items()) и функцию - ключ (lambda x: x[1]), которая определяет, как сравнивать элементы.

Наконец, мы выводим результаты, показывая номер маршрута, рост пассажиропотока и количество поездок в 2001 и 2011 годах.

Этот анализ позволяет определить, какие автобусные маршруты испытали наибольший рост пассажиропотока за десятилетие, что может указывать на изменения в демографических показателях, улучшения в обслуживании или другие интересные тенденции.

Вы можете расширить эти анализы различными способами. Например, вы можете:

  • Проанализировать модели пассажиропотока по дням недели
  • Найти маршруты с снижающимся пассажиропотоком
  • Сравнить сезонные колебания в пассажиропотоке

Техники, которые вы узнали в этом эксперименте, предоставляют прочный фундамент для такого рода исследования и анализа данных.

Резюме

В этом эксперименте вы узнали несколько важных методов манипуляции данными в Python. Они включают чтение и обработку данных из CSV - файлов в словари, использование генераторов списков, множеств и словарей для преобразования данных, а также применение специализированных типов контейнеров из модуля collections. Вы также применили эти навыки для проведения осмысленного анализа данных.

Эти методы являются фундаментальными для анализа данных на Python и ценны в различных реальных сценариях. Способность обрабатывать, преобразовывать и извлекать информацию из данных является важной для программистов на Python. Продолжайте практиковаться с собственными наборами данных, чтобы повысить свою квалификацию в области анализа данных на Python.