Сделана более реалистичная генерация демо данных по людям

This commit is contained in:
2025-11-04 13:38:20 +03:00
parent 52fb50441a
commit ca9a32080d
3 changed files with 66 additions and 9 deletions
+1
View File
@@ -7,3 +7,4 @@ __pycache__/
*.pyc *.pyc
.venv/ .venv/
data/output/ data/output/
data/input/
+59 -6
View File
@@ -1,6 +1,12 @@
""" """
DAG для демонстрации работы с файлами в Airflow DAG для демонстрации работы с файлами в Airflow
Уровень: Средний Уровень: Средний
Этот DAG генерирует русскоязычные данные с использованием библиотеки mimesis, включая:
- ФИО (с учетом пола)
- Возраст
- Зарплату
- Адрес проживания
""" """
import pandas as pd import pandas as pd
import os import os
@@ -24,7 +30,7 @@ dag = DAG(
'file_operations_dag', 'file_operations_dag',
default_args=default_args, default_args=default_args,
description='DAG для изучения работы с файлами в Airflow', description='DAG для изучения работы с файлами в Airflow',
schedule_interval=timedelta(days=1), schedule_interval=None,
catchup=False, catchup=False,
tags=['educational', 'files', 'intermediate'] tags=['educational', 'files', 'intermediate']
) )
@@ -32,33 +38,56 @@ dag = DAG(
def generate_sample_data(): def generate_sample_data():
"""Создание CSV файла с примерными данными""" """Создание CSV файла с примерными данными"""
import pandas as pd import pandas as pd
import random from mimesis import Person, Address, Numeric
from mimesis.enums import Gender
from mimesis.locales import Locale
# Создаем генераторы с русской локализацией
person = Person(Locale.RU)
address = Address(Locale.RU)
# Генерируем данные
names = [person.full_name(gender=Gender.FEMALE if i % 3 == 0 else Gender.MALE) for i in range(1, 101)]
numeric = Numeric()
ages = [numeric.integer_number(start=18, end=65) for _ in range(100)] # Используем правильный метод для генерации возраста
occupations = [person.occupation() for _ in range(100)]
data = { data = {
'id': range(1, 101), 'id': range(1, 101),
'name': [f'User_{i}' for i in range(1, 101)], 'name': names,
'age': [random.randint(18, 65) for _ in range(100)], 'age': ages,
'salary': [random.randint(30000, 100000) for _ in range(100)] 'salary': [max(30000, min(150000, ages[i] * 1200 + (hash(names[i]) % 15000) - 5000)) for i in range(100)],
'address': [address.address() for _ in range(100)]
} }
df = pd.DataFrame(data) df = pd.DataFrame(data)
df.to_csv('/opt/airflow/data/input/sample_data.csv', index=False) df.to_csv('/opt/airflow/data/input/sample_data.csv', index=False)
print(f"Создан файл с {len(df)} записями") print(f"Создан файл с {len(df)} записями")
print(f"Пример данных: {data['name'][0]}, возраст: {data['age'][0]}, зарплата: {data['salary'][0]}, адрес: {data['address'][0]}")
return "sample_data.csv created" return "sample_data.csv created"
def read_and_validate_data(): def read_and_validate_data():
"""Чтение и валидация CSV файла""" """Чтение и валидация CSV файла"""
df = pd.read_csv('/opt/airflow/data/input/sample_data.csv') df = pd.read_csv('/opt/airflow/data/input/sample_data.csv')
print(f"Прочитан файл: {len(df)} строк, {len(df.columns)} столбцов") print(f"Прочитан файл: {len(df)} строк, {len(df.columns)} столбцов")
print(f"Колонки: {list(df.columns)}")
# Простая валидация # Простая валидация
assert len(df) > 0, "Файл пустой" assert len(df) > 0, "Файл пустой"
assert 'name' in df.columns, "Отсутствует столбец name" assert 'name' in df.columns, "Отсутствует столбец name"
assert 'address' in df.columns, "Отсутствует столбец address"
# Дополнительная валидация для новых данных
assert df['name'].notna().all(), "Найдены пустые значения в столбце name"
assert df['address'].notna().all(), "Найдены пустые значения в столбце address"
assert df['age'].between(16, 100).all(), "Некорректные значения возраста"
assert df['salary'].between(20000, 150000).all(), "Некорректные значения зарплаты"
return f"Файл валидирован: {len(df)} записей" return f"Файл валидирован: {len(df)} записей"
def transform_data(): def transform_data():
"""Преобразование данных""" """Преобразование данных"""
import os
df = pd.read_csv('/opt/airflow/data/input/sample_data.csv') df = pd.read_csv('/opt/airflow/data/input/sample_data.csv')
# Простое преобразование - добавим столбец с категорией зарплаты # Простое преобразование - добавим столбец с категорией зарплаты
@@ -66,9 +95,21 @@ def transform_data():
lambda x: 'High' if x >= 70000 else 'Medium' if x >= 50000 else 'Low' lambda x: 'High' if x >= 70000 else 'Medium' if x >= 50000 else 'Low'
) )
# Добавим дополнительные трансформации для новых данных
# Извлечем город из адреса (первое слово в адресе часто является городом)
df['city'] = df['address'].str.split().str[0]
# Преобразуем возраст в возрастную категорию
df['age_category'] = df['age'].apply(
lambda x: 'Young' if x < 30 else 'Middle-aged' if x < 50 else 'Senior'
)
# Создаем директорию, если она не существует
os.makedirs('/opt/airflow/data/output', exist_ok=True)
# Сохраняем обработанные данные # Сохраняем обработанные данные
df.to_csv('/opt/airflow/data/output/processed_data.csv', index=False) df.to_csv('/opt/airflow/data/output/processed_data.csv', index=False)
print(f"Обработаны данные: {len(df)} записей") print(f"Обработаны данные: {len(df)} записей")
print(f"Добавлены новые столбцы: city, age_category")
return f"Данные обработаны: {len(df)} записей" return f"Данные обработаны: {len(df)} записей"
@@ -79,9 +120,15 @@ def write_summary():
summary = { summary = {
'total_records': len(df), 'total_records': len(df),
'avg_salary': df['salary'].mean(), 'avg_salary': df['salary'].mean(),
'avg_age': df['age'].mean(),
'high_salary_count': len(df[df['salary_category'] == 'High']), 'high_salary_count': len(df[df['salary_category'] == 'High']),
'medium_salary_count': len(df[df['salary_category'] == 'Medium']), 'medium_salary_count': len(df[df['salary_category'] == 'Medium']),
'low_salary_count': len(df[df['salary_category'] == 'Low']) 'low_salary_count': len(df[df['salary_category'] == 'Low']),
'young_count': len(df[df['age_category'] == 'Young']),
'middle_aged_count': len(df[df['age_category'] == 'Middle-aged']),
'senior_count': len(df[df['age_category'] == 'Senior']),
'unique_cities': df['city'].nunique(),
'top_cities': df['city'].value_counts().head(5).to_dict()
} }
# Сохраняем сводку в текстовый файл # Сохраняем сводку в текстовый файл
@@ -89,9 +136,15 @@ def write_summary():
f.write("Сводка по обработанным данным:\n") f.write("Сводка по обработанным данным:\n")
f.write(f"Всего записей: {summary['total_records']}\n") f.write(f"Всего записей: {summary['total_records']}\n")
f.write(f"Средняя зарплата: {summary['avg_salary']:.2f}\n") f.write(f"Средняя зарплата: {summary['avg_salary']:.2f}\n")
f.write(f"Средний возраст: {summary['avg_age']:.2f}\n")
f.write(f"Высокая зарплата: {summary['high_salary_count']}\n") f.write(f"Высокая зарплата: {summary['high_salary_count']}\n")
f.write(f"Средняя зарплата: {summary['medium_salary_count']}\n") f.write(f"Средняя зарплата: {summary['medium_salary_count']}\n")
f.write(f"Низкая зарплата: {summary['low_salary_count']}\n") f.write(f"Низкая зарплата: {summary['low_salary_count']}\n")
f.write(f"Молодые (до 30): {summary['young_count']}\n")
f.write(f"Среднего возраста (30-49): {summary['middle_aged_count']}\n")
f.write(f"Пожилые (50+): {summary['senior_count']}\n")
f.write(f"Уникальных городов: {summary['unique_cities']}\n")
f.write(f"Топ-5 городов: {summary['top_cities']}\n")
print("Создана сводка по данным") print("Создана сводка по данным")
return "Сводка создана" return "Сводка создана"
+3
View File
@@ -7,5 +7,8 @@ psycopg2-binary==2.9.9
# Data processing (used in data_processing_dag.py and file_operations_dag.py) # Data processing (used in data_processing_dag.py and file_operations_dag.py)
pandas==2.1.4 pandas==2.1.4
# Data generation library (used in file_operations_dag.py)
mimesis==15.1.0
# Airflow PostgreSQL provider (used in sql_basic_dag.py and data_processing_dag.py) # Airflow PostgreSQL provider (used in sql_basic_dag.py and data_processing_dag.py)
apache-airflow-providers-postgres==5.11.1 apache-airflow-providers-postgres==5.11.1