Как я построил тысячи строк точек данных с нуля

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

В ситуациях, когда данные недоступны, но необходимы, вам придется прибегать к их самостоятельному накоплению. Есть много методов, которые вы можете использовать для получения этих данных от веб-сканирования до API. Но иногда вам нужно создавать поддельные или «фиктивные» данные. Фиктивные данные могут быть полезны в тех случаях, когда вы точно знаете, какие функции вы будете использовать и какие типы данных будете использовать, но у вас просто нет самих данных.

Здесь я покажу вам, как я создал 100 000 строк фиктивных данных. Эти данные также не являются чисто случайными. Если бы это было так, построить было бы намного проще. Иногда при создании фиктивных данных с нуля вам нужно развить в данных тенденции или закономерности, которые могут отражать поведение в реальном мире. Позже вы поймете, что я имею в виду.

«Подпишитесь на Среднее членство здесь, чтобы получить неограниченный доступ и поддержку контента, подобного моему! С вашей поддержкой я зарабатываю небольшую часть членских взносов. Спасибо!"

Необходимость создания набора данных

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

Эта база пользователей также должна в некоторой степени точно отражать реальных пользователей и тенденции, чтобы она не могла быть полностью случайной. Например, вы не хотите, чтобы пользователь имел высшее образование, которому тоже исполнилось 10 лет. Или вы можете не захотеть завышать представительность конкретной точки данных, например, больше мужчин, чем женщин. Все это необходимо учитывать при создании набора данных.

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

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

Построение набора данных

Чтобы писать код, начните с импорта следующих библиотек:

import pandas as pd
import uuid
import random
from faker import Faker
import datetime

Размер

Размер набора данных будет составлять 100 000 точек данных (вы можете сделать больше, но это может занять больше времени). Я присвоил это количество постоянной переменной, которую использовал повсюду:

num_users = 100000

Функции

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

  • ID - уникальная строка символов для идентификации каждого пользователя.
  • Пол - строковый тип данных из трех вариантов.
  • Подписчик - бинарный выбор статуса подписки (Истина / Ложь).
  • Имя - строковый тип данных имени и фамилии пользователя.
  • Электронная почта - строковый тип данных электронного адреса пользователя.
  • Последний вход - строковый тип данных последнего входа в систему.
  • Дата рождения - строковый формат год-месяц-число.
  • Образование - текущий уровень образования в виде строкового типа данных.
  • Биография - короткие строковые описания случайных слов.
  • Рейтинг - целочисленный тип оценки чего-либо от 1 до 5.

Я ввел это как список функций для инициализации фрейма данных Pandas:

# A list of 10 features
features = [
    "id",
    "gender",
    "subscriber",
    "name",
    "email",
    "last_login",
    "dob",
    "education",
    "bio",
    "rating"
]
# Creating a DF for these features
df = pd.DataFrame(columns=features)

Создание несбалансированных данных

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

ID

Для атрибута ID я использовал библиотеку uuid для генерации случайной строки символов 100 000 раз. Затем я назначил его атрибуту ID в фрейме данных.

df['id'] = [uuid.uuid4().hex for i in range(num_users)]

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

print(df['id'].nunique()==num_users)

Будет возвращено значение True, если все идентификаторы в наборе данных уникальны.

Пол

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

Для пола я предоставил три варианта: мужской, женский и па. Однако, если бы я использовал библиотеку Python random, то каждый вариант мог бы быть одинаково показан в наборе данных. В действительности, мужчин и женщин было бы значительно больше, чем на выбор. Поэтому я решил отобразить этот дисбаланс в данных:

genders = ["male", "female", "na"]
df['gender'] = random.choices(
    genders, 
    weights=(47,47,6), 
    k=num_users
)

Используя библиотеку random, я предоставил функции choices() список вариантов пола, веса для каждого выбора и количество вариантов, представленных «k». Затем он был назначен атрибуту «пол» фрейма данных. Дисбаланс, который я описал ранее, представлен в разделе весов, где вариант «нет» появляется примерно в 6% случаев.

Подписчик

Для этого атрибута можно произвольно выбрать значение «Истина» или «Ложь». Поскольку можно обоснованно ожидать, что около половины пользователей будут подписчиками.

choice = [True, False]
df['subscriber'] = random.choices(
    choice, 
    k=num_users
)

Точно так же, как раньше «Пол», я использовал random.choices(), но без весов, потому что этот атрибут можно случайным образом разделить между двумя вариантами.

Имя

Здесь я использовал библиотеку Faker, чтобы создать тысячи имен для всех этих пользователей. Библиотека Faker отлично подходит в этой ситуации, потому что в ней есть возможность для мужских и женских имен. Чтобы обрабатывать гендерные имена, я создал функцию для присвоения имен на основе данного пола.

# Instantiating faker
faker = Faker()
def name_gen(gender):
    """
    Quickly generates a name based on gender
    """
    if gender=='male':
        return faker.name_male()
    elif gender=='female':
        return faker.name_female()
    
    return faker.name()
# Generating names for each user
df['name'] = [name_gen(i) for i in df['gender']]

Я использовал свою простую функцию, чтобы быстро создать список имен на основе данных атрибута «Пол» ранее и назначить его фрейму данных.

Эл. адрес

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

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

Теперь, чтобы надлежащим образом использовать назначение этой функции, я создал цикл, который при необходимости повторно запускал бы функцию во время итерации по атрибуту «Name». Цикл будет повторять выполнение функции до тех пор, пока не будет создано уникальное имя электронной почты.

emails = []
for name in df['name']:
    
    # Generating the email
    email = emailGen(name)
    
    # Looping until a unique email is generated
    while email in emails:
        
        # Creating an email with a random number
        email = emailGen(name, duplicateFound=True)
    
    # Attaching the new email to the list
    emails.append(email)
    
df['email'] = emails

После того, как все электронные письма были созданы, я назначил их атрибуту «Электронная почта» фрейма данных. Вы также можете выполнить дополнительную проверку, чтобы убедиться, что каждое электронное письмо уникально, с помощью того же метода, что и идентификаторы.

Последний Войти

Этот атрибут теперь требует особого форматирования, которое стало проще с использованием библиотеки datetime. Здесь я хотел, чтобы у пользователей была история входа в систему за последний месяц или около того. Я использовал другую пользовательскую функцию, чтобы помочь:

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

Дата рождения

Этот атрибут очень прост, поскольку он похож на «Последний вход». Все, что я сделал, это изменил формат времени, убрав часы, минуты и секунды. Я снова использовал datetime, чтобы помочь случайным образом выбрать дату для каждого пользователя, но на этот раз диапазон времени начался с 1980 по 2006 год, чтобы получить хорошее случайное распределение возрастов. Приведенный ниже код в основном такой же, как и раньше, но с другим форматом и диапазоном дат:

Образование

Атрибут «образование» зависит от «dob». В этих случаях уровень образования зависит от возраста пользователя, а не от самого высокого уровня образования, который он получил. Это еще один из тех атрибутов, когда случайный выбор уровня образования не отражает реальных мировых тенденций.

Я создал еще одну простую функцию, которая проверяет возраст пользователя на основе сегодняшней даты и возвращает его текущий уровень образования.

После создания списка уровней образования я назначил его фреймворку данных.

Био

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

Чтобы учесть этот аспект, я создал функцию, которая проверяет статус их подписки и возвращает случайное предложение от Faker, которое различается по длине.

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

Рейтинг

Чтобы завершить этот набор данных, я хотел включить числовой тип данных. Я выбрал целочисленный тип «rating». Рейтинг от 1 до 5 обозначает что угодно, и он используется для любых целей.

Что касается самих оценок, я решил сместить распределение от 1 до 5 в сторону крайностей, чтобы отразить тенденцию пользователей к более точным оценкам.

# The different ratings available
ratings = [1,2,3,4,5]
# Weighted ratings with a skew towards the ends
df['rating'] = random.choices(
    ratings, 
    weights=(30,10,10,10,30), 
    k=num_users
)

Я снова использовал функцию random.choices(), но со смещением веса в сторону 1 и 5.

Сохранение набора данных

Теперь, когда данные завершены, и если вы писали код, не стесняйтесь просматривать фрейм данных, прежде чем вы решите сохранить его. Если все в порядке, сохраните фрейм данных как .csv файл с помощью этой простой команды:

df.to_csv('dataset.csv')

При этом набор данных сохраняется в виде довольно большого CSV-файла в вашем локальном каталоге. И если вы хотите проверить свой сохраненный набор данных, используйте эту команду для его просмотра:

pd.read_csv('dataset.csv', index_col=0)

Все должно выглядеть хорошо, и теперь, если хотите, вы можете выполнить базовую визуализацию данных. Если вы хотите узнать больше о визуализации данных, прочтите мою следующую статью:



Заключительные мысли

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

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

Не являетесь средним участником? Нажмите здесь, чтобы поддержать Medium и меня!

Код на Github