datafake 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datafake-0.1.0/PKG-INFO +10 -0
- datafake-0.1.0/README.md +0 -0
- datafake-0.1.0/datafake/__init__.py +28 -0
- datafake-0.1.0/datafake/elections.py +59 -0
- datafake-0.1.0/datafake/flights.py +60 -0
- datafake-0.1.0/datafake/football.py +74 -0
- datafake-0.1.0/datafake/health.py +53 -0
- datafake-0.1.0/datafake/movies.py +47 -0
- datafake-0.1.0/datafake/music.py +49 -0
- datafake-0.1.0/datafake/sales.py +61 -0
- datafake-0.1.0/datafake/social.py +52 -0
- datafake-0.1.0/datafake/users.py +51 -0
- datafake-0.1.0/datafake/utils.py +54 -0
- datafake-0.1.0/datafake/weather.py +59 -0
- datafake-0.1.0/datafake.egg-info/PKG-INFO +10 -0
- datafake-0.1.0/datafake.egg-info/SOURCES.txt +20 -0
- datafake-0.1.0/datafake.egg-info/dependency_links.txt +1 -0
- datafake-0.1.0/datafake.egg-info/requires.txt +3 -0
- datafake-0.1.0/datafake.egg-info/top_level.txt +1 -0
- datafake-0.1.0/pyproject.toml +22 -0
- datafake-0.1.0/setup.cfg +4 -0
- datafake-0.1.0/tests/test_generators.py +0 -0
datafake-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datafake
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate realistic synthetic datasets for testing, education and development
|
|
5
|
+
Project-URL: Homepage, https://github.com/reginacabralc/datafake
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pandas>=1.3.0
|
|
9
|
+
Requires-Dist: numpy>=1.21.0
|
|
10
|
+
Requires-Dist: faker>=18.0.0
|
datafake-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# cada import importa una función de su archivo correspondiente (con el . busca dentro del propio archivo)
|
|
2
|
+
from .sales import generate_sales
|
|
3
|
+
from .users import generate_users
|
|
4
|
+
from .football import generate_football
|
|
5
|
+
from .music import generate_music
|
|
6
|
+
from .weather import generate_weather
|
|
7
|
+
from .social import generate_social
|
|
8
|
+
from .movies import generate_movies
|
|
9
|
+
from .health import generate_health
|
|
10
|
+
from .flights import generate_flights
|
|
11
|
+
from .elections import generate_elections
|
|
12
|
+
|
|
13
|
+
# define las funciones públicas (lo que se exporta cuando alguien hace from datafake import *)
|
|
14
|
+
__all__ = [
|
|
15
|
+
"generate_sales",
|
|
16
|
+
"generate_users",
|
|
17
|
+
"generate_football",
|
|
18
|
+
"generate_music",
|
|
19
|
+
"generate_weather",
|
|
20
|
+
"generate_social",
|
|
21
|
+
"generate_movies",
|
|
22
|
+
"generate_health",
|
|
23
|
+
"generate_flights",
|
|
24
|
+
"generate_elections",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# guarda la versión de la librería (debe coincidir siempre con la versión en pyproject.toml)
|
|
28
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
def generate_elections(n=500, seed=42, missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos sintético de elecciones ficticias.
|
|
13
|
+
Parámetros:
|
|
14
|
+
n : int
|
|
15
|
+
Número de filas a generar.
|
|
16
|
+
seed : int
|
|
17
|
+
Semilla aleatoria para reproducibilidad.
|
|
18
|
+
missing_rate : float
|
|
19
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
20
|
+
Devuelve: pd.DataFrame
|
|
21
|
+
"""
|
|
22
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
23
|
+
np.random.seed(seed)
|
|
24
|
+
Faker.seed(seed)
|
|
25
|
+
|
|
26
|
+
# define los valores posibles para las columnas categóricas
|
|
27
|
+
candidates = ["Candidate A", "Candidate B", "Candidate C", "Candidate D"]
|
|
28
|
+
parties = ["Progressive Party", "Conservative Party", "Green Party", "Liberal Party"]
|
|
29
|
+
election_types = ["Presidential", "Legislative", "Municipal", "Regional"]
|
|
30
|
+
# genera nombres de regiones usando Faker y divide entre el número de candidatos porque cada región generará una fila por candidato
|
|
31
|
+
regions = [fake.state() for _ in range(n // len(candidates))]
|
|
32
|
+
|
|
33
|
+
rows = [] # inicializa una lista vacía que irá llenando fila por fila
|
|
34
|
+
for region in regions: # itera sobre cada región generada
|
|
35
|
+
total_votes = np.random.randint(10000, 500000) # genera el total de votos de la región entre 10,000 y 500,000 (1 solo número por región)
|
|
36
|
+
raw = np.random.dirichlet(alpha=[3, 2, 1, 1]) * total_votes # genera 4 proporciones que usman exactamente 1 usando distribución Dirichlet, luego las múltiplica por el total de votos. alpha hace que el candidato A tenga ventaja sobre todos los demás (imitando realidad)
|
|
37
|
+
votes_per_candidate = raw.astype(int) # convierte los votos a enteros (pequeña pérdida de precisión)
|
|
38
|
+
|
|
39
|
+
for i, candidate in enumerate(candidates): # itera sobre cada candidato (el índice i se usa para asignar el partido correspondiente)
|
|
40
|
+
rows.append({ # agrega una fila al listado
|
|
41
|
+
"record_id": f"ELEC_{str(len(rows)).zfill(6)}", # genera ids únicos y secuenciales usando len(rows) como contador
|
|
42
|
+
"region": region,
|
|
43
|
+
"election_type": np.random.choice(election_types), # escoge tipo de elección aleatoriamente con probabilidad uniforme
|
|
44
|
+
"year": np.random.choice([2020, 2021, 2022, 2023, 2024]), # escoge año de elección con probabilidad uniforme
|
|
45
|
+
"candidate": candidate,
|
|
46
|
+
"party": parties[i],
|
|
47
|
+
"votes": votes_per_candidate[i],
|
|
48
|
+
"total_votes_region": total_votes,
|
|
49
|
+
"turnout_pct": np.round(np.random.uniform(40, 85), 1), # genera participación electoral con distribución uniforme continua entre 40% y 85%
|
|
50
|
+
"incumbent": candidate == "Candidate A", # marca al candidato A como incumbente
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
df = pd.DataFrame(rows[:n])
|
|
54
|
+
|
|
55
|
+
# calcula el porcentaje de votos de cada candidato en su región
|
|
56
|
+
df["vote_share_pct"] = np.round(df["votes"] / df["total_votes_region"] * 100, 2)
|
|
57
|
+
|
|
58
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
59
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
fake = Faker()
|
|
7
|
+
|
|
8
|
+
def generate_flights(n=500, seed=42, missing_rate=0.0):
|
|
9
|
+
# si al llamar la función no da parámetros, genera por default 500 filas con seed 42 y sin NaNs
|
|
10
|
+
"""
|
|
11
|
+
Genera un conjunto de datos de vuelos.
|
|
12
|
+
Parámetros:
|
|
13
|
+
n : int
|
|
14
|
+
Número de filas a generar
|
|
15
|
+
semilla (seed): int
|
|
16
|
+
Semilla aleatoria para garantizar la reproducibilidad
|
|
17
|
+
tasa_faltante (missing_rate): float
|
|
18
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0)
|
|
19
|
+
Devuelve: pd.DataFrame
|
|
20
|
+
"""
|
|
21
|
+
# inicializa la semilla aleatoria y en faker. Hay que fijar ambos para garantizar reproducibilidad
|
|
22
|
+
np.random.seed(seed)
|
|
23
|
+
Faker.seed(seed)
|
|
24
|
+
|
|
25
|
+
# define listas con los valores posibles
|
|
26
|
+
airlines = ["Delta", "United Airlines", "American", "Lufthansa", "Emirates",
|
|
27
|
+
"Ryanair", "Air France", "British Airways", "Aeromexico", "LATAM"]
|
|
28
|
+
airports = ["JFK", "LAX", "LHR", "CDG", "DXB", "NRT", "AICM", "GRU", "SYD", "FRA"]
|
|
29
|
+
statuses = ["on_time", "delayed", "cancelled", "diverted"]
|
|
30
|
+
classes = ["economy", "business", "first"]
|
|
31
|
+
|
|
32
|
+
origins = np.random.choice(airports, size=n) # escoge aleatoriamente n aeropuertos de origen
|
|
33
|
+
|
|
34
|
+
# Para cada origen o, filtra la lista de aeropuertos para excluir ese mismo aeropuerto (if a != o),
|
|
35
|
+
# y luego escoge uno al azar. Esto garantiza que ningún vuelo tenga origen igual a destino.
|
|
36
|
+
# El np.array() convierte la lista resultante en array de numpy.
|
|
37
|
+
destinations = np.array([np.random.choice([a for a in airports if a != o]) for o in origins])
|
|
38
|
+
|
|
39
|
+
# Genera n estatus de vuelo con probabilidades no uniformes: 70% a tiempo, 22% retrasado, 5% cancelado y 3% desviado.
|
|
40
|
+
# Estás probabilidades se escogen con la intensión de imitar la realidad del sector áereo en el mundo real
|
|
41
|
+
flight_statuses = np.random.choice(statuses, size=n, p=[0.70, 0.22, 0.05, 0.03])
|
|
42
|
+
|
|
43
|
+
df = pd.DataFrame({
|
|
44
|
+
"flight_id": [f"FLT_{str(i).zfill(6)}" for i in range(1, n + 1)], #Genera IDs únicos tipo FLT_000001. zfill(6) rellena con ceros a la izquierda hasta tener 6 dígitos
|
|
45
|
+
"date": [fake.date_between(start_date="-1y", end_date="today") for _ in range(n)], # genera n fechas aleatorias entre hace 1 año y hoy
|
|
46
|
+
"airline": np.random.choice(airlines, size=n), # escoge aleatoriamente n aerolíneas con probabilidad uniforme
|
|
47
|
+
"origin": origins, # asigna el array que calculamos antes
|
|
48
|
+
"destination": destinations, # asigna los array que calculamos antes
|
|
49
|
+
"class": np.random.choice(classes, size=n, p=[0.75, 0.20, 0.05]), # Escoge clase de un vuelo con probabilidades no uniformes: 75% economy, 20% business y 5% primera clase (imitando la realidad)
|
|
50
|
+
"duration_min": np.random.randint(60, 900, size=n), # Genera duración del vuelo en minutos con distribución uniforme entre 60 y 900 minutos. Este es el rango razonable para vyelos entre los aeropuertos de la lista
|
|
51
|
+
"distance_km": np.random.randint(200, 15000, size=n), # Genera distancia en kilómetros con distribución uniforme entre 200 km y 15,000 km. Es uniforme porque sin saber el par origen-destino exacto no podemos calcular la distancia real. Sin embargo, estos rangos cubren las distancias entre los aeropuertos de nuestra lista
|
|
52
|
+
"price_usd": np.round(np.random.lognormal(mean=5.5, sigma=1.0, size=n), 2), # Genera precios con distribución lognormal (muchos vuelos baratos y pocos muy caros). Mean=5.5 en escala logarítmica equivale a un precio "típico" de 245 dólares
|
|
53
|
+
"status": flight_statuses, # asigna el array que calculamos antes
|
|
54
|
+
"delay_min": np.where(flight_statuses == "delayed", np.random.randint(15, 300, size=n), 0), # Si el estatus es "delayed" np.where asigna un número aleatorio entre 15 y 300 minutos, si no asigna 0
|
|
55
|
+
"passengers": np.random.randint(50, 400, size=n), # Genera número de pasajeros con distribución uniforme entre 50 y 400 (aviones pequeños o grandes, vacíos o totalmente llenos)
|
|
56
|
+
"satisfaction": np.round(np.random.uniform(1, 10, size=n), 1), # Genera una calificaci´øn de satisfacción entre 1,0 y 10.0 con distribución uniforme. Se usa uniforme porque en encuestas de satisfacción los valores extremos son muy comunes
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
# Antes de devolver el dataFrame, pasa por inject_missing. Si missing_rate=0.0 lo devuelve intacto, si no introduce NaNs en las columnas no protegidas
|
|
60
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
def generate_football(n=500, seed=42, missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos sintético de partidos de fútbol.
|
|
13
|
+
Parámetros:
|
|
14
|
+
n : int
|
|
15
|
+
Número de filas a generar.
|
|
16
|
+
seed : int
|
|
17
|
+
Semilla aleatoria para reproducibilidad.
|
|
18
|
+
missing_rate : float
|
|
19
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
20
|
+
Devuelve: pd.DataFrame
|
|
21
|
+
"""
|
|
22
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
23
|
+
np.random.seed(seed)
|
|
24
|
+
Faker.seed(seed)
|
|
25
|
+
|
|
26
|
+
# define los valores posibles para las columnas categóricas
|
|
27
|
+
teams = [
|
|
28
|
+
"Real Madrid", "Barcelona", "Manchester City", "Liverpool",
|
|
29
|
+
"Bayern Munich", "PSG", "Juventus", "Chelsea", "Arsenal",
|
|
30
|
+
"Atletico Madrid", "Borussia Dortmund", "Inter Milan",
|
|
31
|
+
"AC Milan", "Ajax", "Porto"
|
|
32
|
+
]
|
|
33
|
+
competitions = ["La Liga", "Premier League", "Bundesliga", "Serie A", "Champions League"]
|
|
34
|
+
stages = ["Group Stage", "Round of 16", "Quarter Final", "Semi Final", "Final", "Regular Season"]
|
|
35
|
+
|
|
36
|
+
# escofe n equipos locales aleatoriamente de la lista con probabilidad uniforme
|
|
37
|
+
home_teams = np.random.choice(teams, size=n)
|
|
38
|
+
# para cada equipo local h, filtra la lista para excluirlo y escoge un visitante al azar (garantiza que ningún equipo juegue contra sí mismo)
|
|
39
|
+
away_teams = np.array([np.random.choice([t for t in teams if t != h]) for h in home_teams])
|
|
40
|
+
# genera goles con distribución Poisson (el equipo local tiene lam=1.5 y el visitante lam=1.1 porque estadísticamente los equipos locales marcan más goles (fenómeno conocido como "ventaja de local"))
|
|
41
|
+
home_goals = np.random.poisson(lam=1.5, size=n)
|
|
42
|
+
away_goals = np.random.poisson(lam=1.1, size=n)
|
|
43
|
+
|
|
44
|
+
# función auxiliar que recibe los goles de ambos equipos y devuelve el resultado textual
|
|
45
|
+
def get_result(h, a):
|
|
46
|
+
if h > a:
|
|
47
|
+
return "home_win"
|
|
48
|
+
elif a > h:
|
|
49
|
+
return "away_win"
|
|
50
|
+
else:
|
|
51
|
+
return "draw"
|
|
52
|
+
|
|
53
|
+
df = pd.DataFrame({
|
|
54
|
+
"match_id": [f"MATCH_{str(i).zfill(5)}" for i in range(1, n + 1)], # genera ids únicos tipo MATCH_00001
|
|
55
|
+
"date": [fake.date_between(start_date="-3y", end_date="today") for _ in range(n)], # genera n fechas aleatorias en los últimos 3 años usando faker
|
|
56
|
+
"competition": np.random.choice(competitions, size=n, p=[0.25, 0.25, 0.20, 0.20, 0.10]), # escoge competición con probabilidades no uniformes (La Liga y Premier League son las más comunes y Champion League la menos porque tiene menos partidos que una liga completa)
|
|
57
|
+
"stage": np.random.choice(stages, size=n), # escoge etapa con probabilidad uniforme
|
|
58
|
+
"home_team": home_teams, # asigna el array generado antes
|
|
59
|
+
"away_team": away_teams, # asigna el array generado antes
|
|
60
|
+
"home_goals": home_goals, # asigna el array generado antes
|
|
61
|
+
"away_goals": away_goals, # asigna el array generado antes
|
|
62
|
+
"total_goals": home_goals + away_goals, # suma los goles de ambos equipos
|
|
63
|
+
"result": [get_result(h, a) for h, a in zip(home_goals, away_goals)], # aplica la función auxiliar a cada par de goles
|
|
64
|
+
"attendance": np.random.randint(10000, 90000, size=n), # genera asistencia con distribución uniforme discreta entre 10,000 y 90,000 (cubre desde estadios pequeños hasta los más grandes)
|
|
65
|
+
"home_shots": np.random.randint(5, 25, size=n), # genera tiros con distribución uniforme discreta (rango 5-24)
|
|
66
|
+
"away_shots": np.random.randint(3, 20, size=n), # genera tiros con distribución uniforme discreta (rango 3-19 por "ventaja de local")
|
|
67
|
+
"home_possession": np.round(np.random.uniform(35, 65, size=n), 1), # genera con posesión local con distribución uniforme continua entre 35% y 65% (se limira a ese intervalo porque en fútbol profesional es muy raro ver algo fuera de ese intervalo)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
# calcula la posesión visitante como complemento de la local (ambas deben sumar 100%)
|
|
71
|
+
df["away_possession"] = np.round(100 - df["home_possession"], 1)
|
|
72
|
+
|
|
73
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
74
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
def generate_health(n=500, seed=42, missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos sintético de salud/pacientes.
|
|
13
|
+
Parámetros:
|
|
14
|
+
n : int
|
|
15
|
+
Número de filas a generar.
|
|
16
|
+
seed : int
|
|
17
|
+
Semilla aleatoria para reproducibilidad.
|
|
18
|
+
missing_rate : float
|
|
19
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
20
|
+
Devuelve: pd.DataFrame
|
|
21
|
+
"""
|
|
22
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
23
|
+
np.random.seed(seed)
|
|
24
|
+
Faker.seed(seed)
|
|
25
|
+
|
|
26
|
+
# define los valores posibles para las columnas categóricas
|
|
27
|
+
diagnoses = ["Hypertension", "Diabetes", "Anxiety", "Depression", "Obesity",
|
|
28
|
+
"Asthma", "Arthritis", "Migraine", "Anemia", "None"]
|
|
29
|
+
blood_types = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]
|
|
30
|
+
specialties = ["General", "Cardiology", "Neurology", "Endocrinology", "Psychiatry", "Pediatrics"]
|
|
31
|
+
|
|
32
|
+
df = pd.DataFrame({
|
|
33
|
+
"patient_id": [f"PAT_{str(i).zfill(5)}" for i in range(1, n + 1)], # genera ids únicos tipo PAT_00001
|
|
34
|
+
"age": np.random.randint(1, 91, size=n), # genera edades entre 1 y 90 con distribución uniforme discreta
|
|
35
|
+
"gender": np.random.choice(["M", "F", "Other"], size=n, p=[0.48, 0.48, 0.04]), # escoge género con probabilidades iguales para M y F y con 4% para other
|
|
36
|
+
"blood_type": np.random.choice(blood_types, size=n), # escoge tipo sanguíneo con probabilidad uniforme
|
|
37
|
+
"weight_kg": np.round(np.random.normal(loc=70, scale=15, size=n).clip(30, 200), 1), # genera peso con distribución normal centrada en 70kg y variación de 15kg (.clip evita valores imposibles)
|
|
38
|
+
"height_cm": np.round(np.random.normal(loc=168, scale=12, size=n).clip(100, 220), 1), # genera altura con distribución normal centrada en 168cm (promedio global ente hombres y mujeres)
|
|
39
|
+
"diagnosis": np.random.choice(diagnoses, size=n, p=[0.15, 0.12, 0.10, 0.10, 0.10,
|
|
40
|
+
0.08, 0.08, 0.07, 0.05, 0.15]), # escoge diagnóstico con probabilidades no uniformes basada en prevalencia aproximada
|
|
41
|
+
"specialty": np.random.choice(specialties, size=n), # escoge especialidad con probabilidad uniforme
|
|
42
|
+
"num_consultations": np.random.poisson(lam=3, size=n), # genera número de consultas con distribución poisson (modela conteos de eventos discretos en el tiempo) con promedio de 3 consultas por paciente
|
|
43
|
+
"smoker": np.random.choice([True, False], size=n, p=[0.20, 0.80]), # genera con distribución Bernoulli, 20% fumadores (imitando tasas de tabaquismo en países desarrollados)
|
|
44
|
+
"chronic_condition": np.random.choice([True, False], size=n, p=[0.35, 0.65]), # genera con distribución Bernoulli, 35% condición crónica
|
|
45
|
+
"satisfaction_score": np.round(np.random.uniform(1, 10, size=n), 1), # genera calificación de satisfacción entre 1.0 y 10.0 con distribución unfirome continua (las encuestas de satisfacción tienden a evitar extremos)
|
|
46
|
+
"visit_date": [fake.date_between(start_date="-2y", end_date="today") for _ in range(n)], # genera fechas de visita en los últimos 2 años usando Faker
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
# Calcula el índice de masa corporal con la fórmula oficial
|
|
50
|
+
df["bmi"] = np.round(df["weight_kg"] / (df["height_cm"] / 100) ** 2, 1)
|
|
51
|
+
|
|
52
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
53
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
def generate_movies(n=500, seed=42, missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos sintético de películas/streaming.
|
|
13
|
+
Parámetros:
|
|
14
|
+
n : int
|
|
15
|
+
Número de filas a generar.
|
|
16
|
+
seed : int
|
|
17
|
+
Semilla aleatoria para reproducibilidad.
|
|
18
|
+
missing_rate : float
|
|
19
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
20
|
+
Devuelve: pd.DataFrame
|
|
21
|
+
"""
|
|
22
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
23
|
+
np.random.seed(seed)
|
|
24
|
+
Faker.seed(seed)
|
|
25
|
+
|
|
26
|
+
# define los valores posibles para las columnas categóricas
|
|
27
|
+
genres = ["Action", "Comedy", "Drama", "Horror", "Sci-Fi", "Romance", "Documentary", "Animation"]
|
|
28
|
+
platforms = ["Netflix", "HBO Max", "Disney+", "Amazon Prime", "Apple TV+"]
|
|
29
|
+
ratings = ["G", "PG", "PG-13", "R", "NC-17"]
|
|
30
|
+
languages = ["English", "Spanish", "French", "Japanese", "Chinese", "Italian"]
|
|
31
|
+
|
|
32
|
+
df = pd.DataFrame({
|
|
33
|
+
"movie_id": [f"MOV_{str(i).zfill(5)}" for i in range(1, n + 1)], # genera ids únicos tipo MOV_00001
|
|
34
|
+
"title": [fake.catch_phrase() for _ in range(n)], # genera frases cortas que se usan como títulos con Faker
|
|
35
|
+
"genre": np.random.choice(genres, size=n, p=[0.20, 0.15, 0.20, 0.10, 0.12, 0.10, 0.08, 0.05]), # escoge género con probabilidades no uniformes (acción y drama dominan y animación es el más raro porque hay menos películas animadas en streaming)
|
|
36
|
+
"platform": np.random.choice(platforms, size=n, p=[0.35, 0.20, 0.20, 0.15, 0.10]), # escoge plataforma con probabilidades no uniformes (netflix domina porque tiene el catálogo más grande y Apple TV+ es el más pequeño por ser la plataforma más nueva)
|
|
37
|
+
"release_year": np.random.randint(1970, 2025, size=n), # genera año de lanzamiento entre 1970 y 2024 con distribución uniforme discreta
|
|
38
|
+
"duration_min": np.random.randint(75, 211, size=n), # genera duración entre 75 minutos (película corta) y 210 minutos (película muy larga) con distribución uniforme discreta
|
|
39
|
+
"rating": np.random.choice(ratings, size=n, p=[0.05, 0.15, 0.40, 0.35, 0.05]), # escoge clasificación con probabilidades no uniformes (imitando la realidad)
|
|
40
|
+
"language": np.random.choice(languages, size=n, p=[0.50, 0.20, 0.10, 0.08, 0.07, 0.05]), # escoge idioma con probabilidades no uniformes (inglés es el más usando en el mercado de cine)
|
|
41
|
+
"imdb_score": np.round(np.random.normal(loc=6.5, scale=1.2, size=n).clip(1, 10), 1), # genera calificación con distribución normal centrada en 6.5 (usa normal porque pocas películas se clasifican en los extremos)
|
|
42
|
+
"box_office_usd": np.random.lognormal(mean=17, sigma=2, size=n).astype(int), # genera ganancia en taquilla con lognormal con media 17 (la mayoría gana poca y algunas ganan mucho)
|
|
43
|
+
"sequel": np.random.choice([True, False], size=n, p=[0.20, 0.80]), # genera con distribución Bernoulli (el 20% son secuelas, imitando proporciones reales de la industria)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
47
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
def generate_music(n=500, seed=42, missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos sintético de streaming de música.
|
|
13
|
+
Parámetros:
|
|
14
|
+
n : int
|
|
15
|
+
Número de filas a generar.
|
|
16
|
+
seed : int
|
|
17
|
+
Semilla aleatoria para reproducibilidad.
|
|
18
|
+
missing_rate : float
|
|
19
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
20
|
+
Devuelve: pd.DataFrame
|
|
21
|
+
"""
|
|
22
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
23
|
+
np.random.seed(seed)
|
|
24
|
+
Faker.seed(seed)
|
|
25
|
+
|
|
26
|
+
# define los valores posibles para las columnas categóricas
|
|
27
|
+
genres = ["Pop", "Rock", "Hip-Hop", "Electronic", "Jazz", "Classical", "Reggaeton", "R&B"]
|
|
28
|
+
moods = ["Happy", "Sad", "Energetic", "Chill", "Romantic", "Angry"]
|
|
29
|
+
platforms = ["Spotify", "Apple Music", "YouTube Music", "Tencent Music", "Amazon Music"]
|
|
30
|
+
|
|
31
|
+
df = pd.DataFrame({
|
|
32
|
+
"track_id": [f"TRK_{str(i).zfill(6)}" for i in range(1, n + 1)], # genera ids únicos tipo TRK_000001 (no hay eventos repetidas en el dataset)
|
|
33
|
+
"title": [fake.catch_phrase() for _ in range(n)], # usa catch_phrase() de Faker para generar títulos (genera frases de 2-4 palabras que funcionan como títulos creativos)
|
|
34
|
+
"artist": [fake.name() for _ in range(n)], # usa fake.name() para generar nombres de artistas
|
|
35
|
+
"album": [fake.bs() for _ in range(n)], # usa bs() de Faker (genera frases abstractas que funcionan como nombres de albúmes)
|
|
36
|
+
"genre": np.random.choice(genres, size=n, p=[0.25, 0.15, 0.20, 0.10, 0.05, 0.05, 0.12, 0.08]), # escoge género con probabilidades no uniformes (basado en la cantidad de oyentes en plataformas de streaming)
|
|
37
|
+
"mood": np.random.choice(moods, size=n), # escoge estado de ánimo con probabilidad uniforme
|
|
38
|
+
"duration_seconds": np.random.randint(120, 360, size=n), # genera duración en segundos entre 120 y 359 (casi 6 min) con distribución uniforme
|
|
39
|
+
"release_year": np.random.randint(1980, 2025, size=n), # gener año de lanzamiento entre 1980 y 2024 con distribución uniforme
|
|
40
|
+
"streams": np.random.lognormal(mean=12, sigma=2, size=n).astype(int), # genera reproducciones con distribución lognormal (la mayoría de las canciones tienen pocas reproducciones y unas pocas tienen muchas)
|
|
41
|
+
"likes": np.random.lognormal(mean=8, sigma=2, size=n).astype(int), # genera likes con distribución lognormal
|
|
42
|
+
"platform": np.random.choice(platforms, size=n, p=[0.40, 0.25, 0.20, 0.08, 0.07]), # escoge plataforma con probabilidad no uniforme
|
|
43
|
+
"explicit": np.random.choice([True, False], size=n, p=[0.30, 0.70]), # se genera con distribución Bernoulli
|
|
44
|
+
"bpm": np.random.randint(60, 180, size=n), # genera bpm entre 60 y 179 con distribución uniforme discreta
|
|
45
|
+
"rating": np.round(np.random.uniform(1, 5, size=n), 1), # genera calificación entre 1.0 y 5.o con distribución uniforme continua
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
49
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 5 parámetros (con parámetros default)
|
|
10
|
+
def generate_sales(n=500, seed=42, start_date="2024-01-01", end_date="2024-12-31", missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos de ventas sintético.
|
|
13
|
+
|
|
14
|
+
Parámetros:
|
|
15
|
+
n : int
|
|
16
|
+
Número de filas a generar.
|
|
17
|
+
seed : int
|
|
18
|
+
Semilla aleatoria para reproducibilidad.
|
|
19
|
+
start_date : str
|
|
20
|
+
Fecha de inicio para las transacciones (AAAA-MM-DD).
|
|
21
|
+
end_date : str
|
|
22
|
+
Fecha de fin para las transacciones (AAAA-MM-DD).
|
|
23
|
+
missing_rate : float
|
|
24
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
25
|
+
Devuelve: pd.DataFrame
|
|
26
|
+
"""
|
|
27
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
28
|
+
np.random.seed(seed)
|
|
29
|
+
Faker.seed(seed)
|
|
30
|
+
|
|
31
|
+
# define los valores posibles para las columnas categóricas
|
|
32
|
+
categories = ["Electronics", "Clothing", "Food", "Home", "Sports", "Beauty"]
|
|
33
|
+
payment_methods = ["card", "transfer", "cash", "paypal"]
|
|
34
|
+
statuses = ["completed", "returned", "pending"]
|
|
35
|
+
# crea 200 IDs de clientes y 50 de productos
|
|
36
|
+
# se crean antes del df para que los mismos IDs se repiran en múltiples filas, simulando que un cliente compra varias veces
|
|
37
|
+
customer_ids = [f"CUST_{str(i).zfill(4)}" for i in range(1, 201)]
|
|
38
|
+
product_ids = [f"PROD_{str(i).zfill(4)}" for i in range(1, 51)]
|
|
39
|
+
# genera n fechas distribuidas uniformemente entre start_date y end_date
|
|
40
|
+
dates = pd.date_range(start=start_date, end=end_date, periods=n)
|
|
41
|
+
|
|
42
|
+
df = pd.DataFrame({
|
|
43
|
+
"sale_id": [f"SALE_{str(i).zfill(6)}" for i in range(1, n + 1)], # genera IDs tipo SALE_000001 desde 1 hasta n+1, con zfill(6) rellena con ceros a la izquierda hasta tener 6 digitos
|
|
44
|
+
"date": np.random.choice(dates, size=n), # escoge n fechas aleatoriamente del rango generado, se hace aleatorio para uqe las fechas no sean perfectamente secuenciales
|
|
45
|
+
"customer_id": np.random.choice(customer_ids, size=n), # escoge IDs aleatoriamente del catálogo (con repetidos)
|
|
46
|
+
"product_id": np.random.choice(product_ids, size=n), # escoge IDs aleatoriamente del catálogo (con repetidos)
|
|
47
|
+
"category": np.random.choice(categories, size=n, p=[0.30, 0.20, 0.20, 0.15, 0.10, 0.05]), # escoge categoría con probabilidad no uniforme, imitando patrones reales de comercio (ecommerce)
|
|
48
|
+
"quantity": np.random.randint(1, 10, size=n), # genera cantidades enteras entre 1 y 9 con distribución uniforme discreta
|
|
49
|
+
"unit_price": np.round(np.random.lognormal(mean=4.5, sigma=1.0, size=n), 2), # genera precios con distribución lognormal (muchos productos baratos y pocos muy caros)
|
|
50
|
+
"payment_method": np.random.choice(payment_methods, size=n, p=[0.55, 0.25, 0.12, 0.08]), # escoge método con probabilidades no unfiromes (tarjeta domina y paypal es menos común)
|
|
51
|
+
"status": np.random.choice(statuses, size=n, p=[0.88, 0.07, 0.05]), # escoge estatus con probabilidades no uniformes (la mayoría se completan y pocas quedan pendientes)
|
|
52
|
+
"store": [fake.city() for _ in range(n)], # genera n nombres de ciudades usando Faker. El _ en el for loop indica que la variable iteradora no se usa, solo nos importa repetir la acción n veces
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
df["revenue"] = np.round(df["quantity"] * df["unit_price"], 2) # calcula el ingreso total como quantity*unit_price
|
|
57
|
+
# convierte la columna date a formato fecha sin hora
|
|
58
|
+
df["date"] = pd.to_datetime(df["date"]).dt.date
|
|
59
|
+
|
|
60
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
61
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
def generate_social(n=500, seed=42, missing_rate=0.0):
|
|
11
|
+
"""
|
|
12
|
+
Genera un conjunto de datos sintético de publicaciones en redes sociales.
|
|
13
|
+
Parámetros:
|
|
14
|
+
n : int
|
|
15
|
+
Número de filas a generar.
|
|
16
|
+
seed : int
|
|
17
|
+
Semilla aleatoria para reproducibilidad.
|
|
18
|
+
missing_rate : float
|
|
19
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
20
|
+
Devuelve: pd.DataFrame
|
|
21
|
+
"""
|
|
22
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
23
|
+
np.random.seed(seed)
|
|
24
|
+
Faker.seed(seed)
|
|
25
|
+
|
|
26
|
+
# define los valores posibles para las columnas categóricas
|
|
27
|
+
platforms = ["Instagram", "Twitter", "TikTok", "Facebook", "LinkedIn"]
|
|
28
|
+
content_types = ["image", "video", "text", "story", "reel"]
|
|
29
|
+
topics = ["Food", "Travel", "Tech", "Fashion", "Sports", "Music", "Politics", "Health"]
|
|
30
|
+
|
|
31
|
+
df = pd.DataFrame({
|
|
32
|
+
"post_id": [f"POST_{str(i).zfill(6)}" for i in range(1, n + 1)], # genera IDs únicos tipo POST_000001
|
|
33
|
+
"user_id": [f"USR_{str(np.random.randint(1, 201)).zfill(5)}" for _ in range(n)], # genera IDs de usuario entre 1 y 200 aleatoriamente (mismos usuarios aparecen muchas veces, un usuario con muchos posts)
|
|
34
|
+
"platform": np.random.choice(platforms, size=n, p=[0.30, 0.25, 0.20, 0.15, 0.10]), # escoge plataforma con probabilidades no uniformes (basado en que app tiene más usuarios activos en la realidad)
|
|
35
|
+
"content_type": np.random.choice(content_types, size=n, p=[0.30, 0.25, 0.20, 0.15, 0.10]), # escoge tipo de contenido con probabilidades no uniformes (imagen domina y reel es menos común (aunque está creciendo rápidamente))
|
|
36
|
+
"topic": np.random.choice(topics, size=n), # escoge tema con probabilidad uniforme
|
|
37
|
+
"date": [fake.date_between(start_date="-1y", end_date="today") for _ in range(n)], # genera fechas en el último año usando Faker
|
|
38
|
+
"likes": np.random.lognormal(mean=5, sigma=2, size=n).astype(int), # genera con distribución lognormal con media 5
|
|
39
|
+
"comments": np.random.lognormal(mean=3, sigma=1.5, size=n).astype(int), # genera con distribución lognormal con media 3 (pues normalmente hay más likes que comentarios)
|
|
40
|
+
"shares": np.random.lognormal(mean=2, sigma=1.5, size=n).astype(int), # genera con distribución lognormal con media 2 (pues normalmente hay más comentarios que shares)
|
|
41
|
+
"reach": np.random.lognormal(mean=7, sigma=2, size=n).astype(int), # genera alcance con distribución lognormal con media 7 (siempre mayor que likes porque el alcance incluye personas que vieron el post pero no interactuaron)
|
|
42
|
+
"followers_at_post": np.random.lognormal(mean=8, sigma=2, size=n).astype(int), # genera seguidores al momento del post con distribución lognormal con media 8 (muchas cuentas pequeñas y pocos influencers)
|
|
43
|
+
"is_viral": np.random.choice([True, False], size=n, p=[0.05, 0.95]), # genera con distribución Bernoulli (solo el 5% de posts se vuelven virales, imitando la realidad donde la viralidad es un evento raro)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
# calcula el engagement rate usando la fórmula de marketing digital: suma de interacciones dividida entre alcance multiplicado por 100
|
|
47
|
+
df["engagement_rate"] = np.round(
|
|
48
|
+
(df["likes"] + df["comments"] + df["shares"]) / df["reach"] * 100, 2
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
52
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 3 parámetros (con parámetros default)
|
|
10
|
+
# el default de n es 300 en lugar de 500 porque una base de clientes típicamente tiene menos registros que número de transacciones
|
|
11
|
+
def generate_users(n=300, seed=42, missing_rate=0.0):
|
|
12
|
+
"""
|
|
13
|
+
Genera un conjunto de datos de usuarios sintético.
|
|
14
|
+
|
|
15
|
+
Parámetros:
|
|
16
|
+
n : int
|
|
17
|
+
Número de filas a generar.
|
|
18
|
+
seed : int
|
|
19
|
+
Semilla aleatoria para reproducibilidad.
|
|
20
|
+
missing_rate : float
|
|
21
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
22
|
+
Devuelve: pd.DataFrame
|
|
23
|
+
"""
|
|
24
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
25
|
+
np.random.seed(seed)
|
|
26
|
+
Faker.seed(seed)
|
|
27
|
+
|
|
28
|
+
# define los valores posibles para las columnas categóricas
|
|
29
|
+
plans = ["free", "basic", "pro", "enterprise"]
|
|
30
|
+
devices = ["mobile", "desktop", "tablet"]
|
|
31
|
+
statuses = ["active", "inactive", "churned"]
|
|
32
|
+
|
|
33
|
+
df = pd.DataFrame({
|
|
34
|
+
"user_id": [f"USR_{str(i).zfill(5)}" for i in range(1, n + 1)], # genera ids únicos tipo USR_00001
|
|
35
|
+
"name": [fake.name() for _ in range(n)], # usa faker para generar nombres realistas
|
|
36
|
+
"email": [fake.email() for _ in range(n)], # usa faker para generar emails realistas
|
|
37
|
+
"country": [fake.country() for _ in range(n)], # usa faker para generar países realistas
|
|
38
|
+
"age": np.random.randint(18, 71, size=n), # genera edades entre 18 y 70 con distribución uniforme discreta (asume usuarios adultos)
|
|
39
|
+
"plan": np.random.choice(plans, size=n, p=[0.55, 0.25, 0.15, 0.05]), # escoge plan con probabilidades no uniformes (la mayoría plan gratuito, muy pocos enterprise)
|
|
40
|
+
"device": np.random.choice(devices, size=n, p=[0.55, 0.35, 0.10]), # escoge dispositivo con probabilidades no unfiromes (la mayoría mobile y pocos tablet)
|
|
41
|
+
"sessions_per_month": np.random.poisson(lam=12, size=n), # genera número de sesiones mensaules con distribución poisson. Poisson porque modela conteos de eventos en el tiempo (promedio de 12 al mes)
|
|
42
|
+
"avg_session_minutes": np.round(np.random.lognormal(mean=2.5, sigma=0.8, size=n), 1), # genera duración promedio de sesión con distribución lognormal (la mayoría cortas pero algunas largas)
|
|
43
|
+
"status": np.random.choice(statuses, size=n, p=[0.70, 0.20, 0.10]), # escoge estatus con probabilidades no uniformes
|
|
44
|
+
"registration_date": [fake.date_between(start_date="-3y", end_date="today") for _ in range(n)], # genera fechas de registro en los últimos 3 años usando Faker
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
# crea una columna binaria que se deriva del status, donde status es "churned" pone 1, en los demás 0. Convierte la columna de True/False a entero 1/0
|
|
48
|
+
df["churned"] = (df["status"] == "churned").astype(int)
|
|
49
|
+
|
|
50
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
51
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
# definimos la función que recibe 3 parámetros.
|
|
5
|
+
def inject_missing(df: pd.DataFrame, missing_rate: float = 0.0, seed: int = None):
|
|
6
|
+
"""
|
|
7
|
+
Inyecta valores NaN aleatoriamente en un DataFrame.
|
|
8
|
+
Parámetros:
|
|
9
|
+
df : pd.DataFrame
|
|
10
|
+
El DataFrame al que se le inyectarán valores NaNs.
|
|
11
|
+
missing_rate : float
|
|
12
|
+
Proporción de valores a convertir en NaN (0.0 a 1.0).
|
|
13
|
+
Las columnas de ID y fechas están protegidas y nunca reciben NaNs.
|
|
14
|
+
Por default es 0.
|
|
15
|
+
seed : int
|
|
16
|
+
Semilla aleatoria para reproducibilidad.
|
|
17
|
+
Devuelve: pd.DataFrame
|
|
18
|
+
"""
|
|
19
|
+
# si no se piden NaNs devuelve inmediatamente el DataFrame sin hacer cambios
|
|
20
|
+
if missing_rate == 0.0:
|
|
21
|
+
return df
|
|
22
|
+
|
|
23
|
+
# verifica que el valor esté entre 0 y 1, si no lanza una excepción
|
|
24
|
+
if not 0.0 < missing_rate < 1.0:
|
|
25
|
+
raise ValueError("missing_rate must be between 0.0 and 1.0 (exclusive)")
|
|
26
|
+
|
|
27
|
+
# fija la semilla si es que el usuario pasó una
|
|
28
|
+
if seed is not None:
|
|
29
|
+
np.random.seed(seed)
|
|
30
|
+
|
|
31
|
+
# construye una lista de columnas protegidas que NUNCA deben tener NaNs
|
|
32
|
+
# recorre todo el df y guarda solo las que terminen en id o que sean exactamente "date", "visit_date" o "registration_date"
|
|
33
|
+
protected = [col for col in df.columns if any([
|
|
34
|
+
col.endswith("_id"),
|
|
35
|
+
col.endswith("id"),
|
|
36
|
+
col == "date",
|
|
37
|
+
col == "visit_date",
|
|
38
|
+
col == "registration_date",
|
|
39
|
+
])]
|
|
40
|
+
|
|
41
|
+
# hace una copia del df antes de modificarlo
|
|
42
|
+
df = df.copy()
|
|
43
|
+
|
|
44
|
+
# recorre cada columna. Si está en la lista de protegidas la salta y pasa a la siguiente
|
|
45
|
+
for col in df.columns:
|
|
46
|
+
if col in protected:
|
|
47
|
+
continue
|
|
48
|
+
# genera un array de True/False del mismo largo que el df
|
|
49
|
+
# np.random.random() produce números entre 0 y 1, los que caen por debajo de missing_rate se vuelven True
|
|
50
|
+
mask = np.random.random(size=len(df)) < missing_rate
|
|
51
|
+
# donde mask es True, reemplaza el valor de esa celda por NaN
|
|
52
|
+
df.loc[mask, col] = np.nan
|
|
53
|
+
# devuelve el df
|
|
54
|
+
return df
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from .utils import inject_missing
|
|
5
|
+
|
|
6
|
+
# crea una instancia global de Faker
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
# define la función con 5 parámetros (con parámetros default)
|
|
10
|
+
# tiene sentido controlar el rango de fechas porque es clima
|
|
11
|
+
def generate_weather(n=500, seed=42, start_date="2024-01-01", end_date="2024-12-31", missing_rate=0.0):
|
|
12
|
+
"""
|
|
13
|
+
Genera un conjunto de datos de clima sintético.
|
|
14
|
+
|
|
15
|
+
Parámetros:
|
|
16
|
+
n : int
|
|
17
|
+
Número de filas a generar.
|
|
18
|
+
seed : int
|
|
19
|
+
Semilla aleatoria para reproducibilidad.
|
|
20
|
+
start_date : str
|
|
21
|
+
Fecha de inicio (AAAA-MM-DD).
|
|
22
|
+
end_date : str
|
|
23
|
+
Fecha de fin (AAAA-MM-DD).
|
|
24
|
+
missing_rate : float
|
|
25
|
+
Proporción de valores faltantes a inyectar (0.0 a 1.0).
|
|
26
|
+
Devuelve: pd.DataFrame
|
|
27
|
+
"""
|
|
28
|
+
# fija la semilla en numpy y en Faker por separado para garantizar reproducibilidad completa
|
|
29
|
+
np.random.seed(seed)
|
|
30
|
+
Faker.seed(seed)
|
|
31
|
+
|
|
32
|
+
# define los valores posibles para las columnas categóricas
|
|
33
|
+
cities = [
|
|
34
|
+
"Mexico City", "New York", "London", "Tokyo", "Paris",
|
|
35
|
+
"Sydney", "Toronto", "Berlin", "Dubai", "São Paulo"
|
|
36
|
+
]
|
|
37
|
+
conditions = ["Sunny", "Cloudy", "Rainy", "Stormy", "Snowy", "Windy", "Foggy"] # cubren lo que cualquier ciudad de la lista podría experimentar
|
|
38
|
+
|
|
39
|
+
# genera n fechas distribuidas uniformemente entre start_date y end_date
|
|
40
|
+
dates = pd.date_range(start=start_date, end=end_date, periods=n)
|
|
41
|
+
|
|
42
|
+
df = pd.DataFrame({
|
|
43
|
+
"record_id": [f"WTH_{str(i).zfill(6)}" for i in range(1, n + 1)], # genera IDs únicos tipo WTH_000001
|
|
44
|
+
"date": np.random.choice(dates, size=n), # escoge n fechas aleatoriamente del rango generado para que no sean perfectamente secuenciales
|
|
45
|
+
"city": np.random.choice(cities, size=n), # escoge ciudad con probabilidad uniforme
|
|
46
|
+
"temperature_c": np.round(np.random.normal(loc=18, scale=12, size=n), 1), # genera temperatura con distribución normal, con media 18C y variación 12C amplia que cubre ciduades frías (como Toronto) hasta calurosas (como Dubai)
|
|
47
|
+
"humidity_pct": np.round(np.random.uniform(20, 100, size=n), 1), # genera humedad con distribución uniforme continua entre 20% y 100%
|
|
48
|
+
"wind_speed_kmh": np.round(np.random.lognormal(mean=2.5, sigma=0.8, size=n), 1), # genera velocidad del viento con distribución lognormal (mucho viento suave, a veces viento fuerte)
|
|
49
|
+
"precipitation_mm": np.round(np.random.exponential(scale=5, size=n), 1), # genera precipitación con distribución exponencial (la mayoría de días llueve poco o nada, y ocasionalmente llueve mucho. Sigue comportamiento real de la lluvia)
|
|
50
|
+
"condition": np.random.choice(conditions, size=n, p=[0.30, 0.25, 0.20, 0.08, 0.07, 0.06, 0.04]), # escoge condición con probabilidades no uniformes
|
|
51
|
+
"uv_index": np.random.randint(0, 11, size=n), # genera índice UV entre 0 y 10 con distribución uniforme discreta (no incluimos 11 en el índice porque es un extremo poco común globalmente)
|
|
52
|
+
"visibility_km": np.round(np.random.uniform(1, 30, size=n), 1), # genera visibilidad entre 1km (día nublado) y 30km (día despejado) con distribución uniforme continua
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
# convierte la columna date a fecha sin componente de hora
|
|
56
|
+
df["date"] = pd.to_datetime(df["date"]).dt.date
|
|
57
|
+
|
|
58
|
+
# pasa el DataFrame completo por inject_missing antes de devolverlo
|
|
59
|
+
return inject_missing(df, missing_rate=missing_rate, seed=seed)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datafake
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate realistic synthetic datasets for testing, education and development
|
|
5
|
+
Project-URL: Homepage, https://github.com/reginacabralc/datafake
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pandas>=1.3.0
|
|
9
|
+
Requires-Dist: numpy>=1.21.0
|
|
10
|
+
Requires-Dist: faker>=18.0.0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
datafake/__init__.py
|
|
4
|
+
datafake/elections.py
|
|
5
|
+
datafake/flights.py
|
|
6
|
+
datafake/football.py
|
|
7
|
+
datafake/health.py
|
|
8
|
+
datafake/movies.py
|
|
9
|
+
datafake/music.py
|
|
10
|
+
datafake/sales.py
|
|
11
|
+
datafake/social.py
|
|
12
|
+
datafake/users.py
|
|
13
|
+
datafake/utils.py
|
|
14
|
+
datafake/weather.py
|
|
15
|
+
datafake.egg-info/PKG-INFO
|
|
16
|
+
datafake.egg-info/SOURCES.txt
|
|
17
|
+
datafake.egg-info/dependency_links.txt
|
|
18
|
+
datafake.egg-info/requires.txt
|
|
19
|
+
datafake.egg-info/top_level.txt
|
|
20
|
+
tests/test_generators.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
datafake
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "datafake"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Generate realistic synthetic datasets for testing, education and development"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pandas>=1.3.0",
|
|
13
|
+
"numpy>=1.21.0",
|
|
14
|
+
"faker>=18.0.0"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/reginacabralc/datafake"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["."]
|
|
22
|
+
include = ["datafake*"]
|
datafake-0.1.0/setup.cfg
ADDED
|
File without changes
|