Помимо коллекций и понятий

Пару дней назад я написал пост, в котором резюмировал, сколько были использованы Коллекции и Понимание. Данные были предоставлены в виде lists, либо списков dictionaries, либо tuples. А чтобы ответить на вопросы о данных, автор часто использовал list comprehensions — перебор списков с помощью цикла for. Я начинаю видеть в этом очень ориентированный на Python способ решения проблем.

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

В любом случае, у меня возник зуд вернуться к этой главе и спросить:

Как решить ту же проблему, используя фреймы данных?

Так вот о чем этот пост.

Вы можете ссылаться на эти предыдущие посты для контекста; также имейте в виду, что это небольшое отклонение от книги Джоэла Груса (например, здесь я буду использовать панды и блокнот jupyter, которые не рассматриваются в книге).

Для ознакомления вот данные, которые вы получаете как недавно нанятый специалист по данным в Data Scienster™.

# users in the network
# stored as a list of dictionaries
users = [
    {"id": 0, "name": "Hero"},
    {"id": 1, "name": "Dunn"},
    {"id": 2, "name": "Sue"},
    {"id": 3, "name": "Chi"},
    {"id": 4, "name": "Thor"},
    {"id": 5, "name": "Clive"},
    {"id": 6, "name": "Hicks"},
    {"id": 7, "name": "Devin"},
    {"id": 8, "name": "Kate"},
    {"id": 9, "name": "Klein"}
]
# friendship pairings in the network
# stored as a list of tuples
friendship_pairs = [(0,1), (0,2), (1,2), (1,3), (2,3), (3,4),
                    (4,5), (5,6), (5,7), (6,8), (7,8), (8,9)]
# interests data
# stored as another list of tuples
interests = [
    (0, "Hadoop"), (0, "Big Data"), (0, "HBase"), (0, "Java"),
    (0, "Spark"), (0, "Storm"), (0, "Cassandra"),
    (1, "NoSQL"), (1, "MongoDB"), (1, "Cassandra"), (1, "HBase"),
    (1, "Postgres"), (2, "Python"), (2, "scikit-learn"), (2, "scipy"),
    (2, "numpy"), (2, "statsmodels"), (2, "pandas"), (3, "R"), (3, "Python"),
    (3, "statistics"), (3, "regression"), (3, "probability"),
    (4, "machine learning"), (4, "regression"), (4, "decision trees"),
    (4, "libsvm"), (5, "Python"), (5, "R"), (5, "Java"), (5, "C++"),
    (5, "Haskell"), (5, "programming langauges"), (6, "statistics"),
    (6, "probability"), (6, "mathematics"), (6, "theory"),
    (7, "machine learning"), (7, "scikit-learn"), (7, "Mahout"),
    (7, "neural networks"), (8, "neural networks"), (8, "deep learning"),
    (8, "Big Data"), (8, "artificial intelligence"), (9, "Hadoop"),
    (9, "Java"), (9, "MapReduce"), (9, "Big Data")
    ]

Имея только эти фрагменты данных, мы можем создавать функции, использовать циклы for и понимание списков, чтобы ответить на некоторые вопросы, например:

  • С кем дружит каждый пользователь?
  • Каково общее и среднее количество подключений?
  • Какие пользователи разделяют одинаковые интересы?
  • Какие самые популярные темы в этой сети?

Однако глава заканчивается списками, функциями и пониманием. Как насчет хранения данных во фреймах данных?

Сначала мы сохраним users как фрейм данных:

import pandas as pd
# convert list of dict into dataframe
users_df = pd.DataFrame(users)
users_df

Просто визуально data frame отличается от list of dictionaries:

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

Затем нам дается число list of tuples, представляющее пары дружбы, и мы продолжаем превращать его в число dictionary, используя число dictionary comprehension:

# list of tuples
friendship_pairs = [(0,1), (0,2), (1,2), (1,3), (2,3), (3,4),
                    (4,5), (5,6), (5,7), (6,8), (7,8), (8,9)]
# create a dict, where keys are users id, 
# dictionary comprehension
friendships = {user["id"]: [] for user in users}
for i, j in friendship_pairs:
    friendships[i].append(j)
    friendships[j].append(i)

Как и в предыдущем примере, я обнаружил, что просмотр данных как data frame отличается от просмотра их как dictionary:

С этого момента я делаю несколько операций в пандах, чтобы объединить первые две таблицы, чтобы у меня был столбец с идентификатором пользователя, именем пользователя и идентификатором их первого, второго или , в некоторых случаях третьи друзья (у большинства людей в этой сети есть 3 прямых соединения).

Если вы хотите узнать конкретную операцию панд, вот код:

# The users_df is fine as is with two columns: id and name (see above)
# We'll transform the friendships_df
# reset_index allows us to add an index column
friendships_df.reset_index(inplace=True)
# add index column
friendships_df = friendships_df.rename(columns = {"id":"new column name"})
# change index column to 'id'
friendships_df = friendships_df.rename(columns = {'index':'id'})
# join with users_df so we get each person's name
users_friendships = pd.merge(users_df, friendships_df, on='id')

После того, как мы объединили users_df и friendships_df, у нас есть:

Поскольку у нас есть данные users и friendships, мы могли бы написать функцию, которая поможет нам ответить на вопрос «сколько друзей у каждого пользователя?». Кроме того, нам нужно будет создать list comprehension, чтобы мы перебирали все user внутри users:

# function to count how many friend each user has
def number_of_friends(user):
    """How many friends does _user_ have?"""
    user_id = user["id"]
    friend_ids = friendships[user_id]
    return len(friend_ids)
# list comprehension to apply the function for each user
num_friends_by_id = [(user["id"], number_of_friends(user)) for user in users]
# this gives us a list of tuples
num_friends_by_id
[(0, 2),
 (1, 3),
 (2, 3),
 (3, 3),
 (4, 2),
 (5, 3),
 (6, 2),
 (7, 2),
 (8, 3),
 (9, 1)]

Опять же, просмотр данных в виде list of tuples отличается от data frame, поэтому давайте продолжим и превратим это во фрейм данных pandas:

# when converting to data frame, we can set the name of the columns to id and num_friends; this sets us up for another join
num_friends_by_id = pd.DataFrame(num_friends_by_id, columns = ['id', 'num_friends'])

Поскольку у нас есть столбец «id», мы можем соединить его с нашим ранее созданным фреймом данных users_friendships:

После объединения с users_friendships с помощью функции merge мы получаем (users_friendships2):

Теперь вы знакомы с процессом. У нас есть коллекция Python, обычно list из dictionaries или tuples, и мы хотим преобразовать их в data frame.

Мы повторим этот процесс для переменной interests, которая является длинной list of tuples (см. выше). Мы преобразуем его во фрейм данных, затем соединим с users_friendships_2, чтобы получить более длинный фрейм данных с interests в качестве одного из столбцов (примечание: изображение обрезано для пробела):

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

Например, я могу захотеть, чтобы все пользователи интересовались «большими данными»:

Раньше нам пришлось бы создавать функцию, которая возвращает list comprehension:

def data_scientists_who_like(target_interest):
    """Find the ids of all users who like the target interests."""
    return [user_id
            for user_id, user_interest in interests
            if user_interest == target_interest]
data_scientists_who_like("Big Data")

Фрейм данных имеет и другие преимущества, вы также можете запрашивать столбцы по нескольким условиям, вот два способа запроса нескольких тем:

# Option One: Use .query()
user_friendship_topics.query('topic == "machine learning" | topic == "regression" | topic == "decision trees" | topic == "libsvm"')
# Option Two: Use .isin()
user_friendship_topics[user_friendship_topics['topic'].isin(["machine learning", "regression", "decision trees", "libsvm"])]

Оба варианта возвращают этот фрейм данных:

Запросив фрейм данных, мы узнали:

  • все пользователи, интересующиеся этими четырьмя темами
  • пользователи, у которых есть общие интересы с Тором
  • (при необходимости) num_friends, которые есть у каждого пользователя

Вы также можете узнать самые популярные темы в этой сети:

# groupby topic, tally(count), then reset_index(), then sort
user_friendship_topics.groupby(['topic']).count().reset_index().sort_values('id', ascending=False)

Вы даже можете groupby два столбца (имя и тема), чтобы увидеть темы интересов, перечисленные каждым пользователем:

user_friendship_topics.groupby(['name', 'topic']).count()

Надеемся, вы убедились, что фреймы данных являются мощным дополнением к более знакомым операциям в Python, таким как циклы for и/или понимание списков; что оба стоит хорошо знать, чтобы манипулировать данными в различных форматах. (например, для доступа к данным JSON словари Python предпочтительнее, чем фреймы данных).

***

Чтобы узнать больше о науке о данных, машинном обучении, R, Python, SQL и многом другом, найдите меня в Twitter.