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.
@@ -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
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,3 @@
1
+ pandas>=1.3.0
2
+ numpy>=1.21.0
3
+ faker>=18.0.0
@@ -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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes