import pandas as pd import numpy as np from faker import Faker from datetime import datetime, timedelta, date import random def generate_synthetic_data(num_customers=1000): """ Generate synthetic customer data for e-commerce analysis. This function creates a dataset of customers with various attributes such as demographics, purchase history, and preferences. It uses the Faker library to generate realistic-looking data for Ukrainian customers. Args: num_customers (int): The number of customer records to generate (default: 1000) Returns: pandas.DataFrame: A DataFrame containing the generated customer data """ # Set up Faker for Ukrainian locale fake = Faker('uk_UA') Faker.seed(42) np.random.seed(42) # Define constants NUM_CUSTOMERS = num_customers START_DATE = date(2019, 1, 1) END_DATE = date(2024, 7, 31) # Helper functions def generate_phone_number(): """Generate a realistic Ukrainian phone number.""" return f"+380{random.randint(50, 99)}{fake.msisdn()[6:]}" def generate_email(name): """Generate an email address based on the customer's name.""" username = name.lower().replace(' ', '.').replace('\'', '') domain = random.choice(['gmail.com', 'ukr.net', 'i.ua', 'meta.ua', 'yahoo.com']) return f"{username}@{domain}" # Define regions and their characteristics REGIONS = { 'Київська': {'avg_age': 40, 'urbanization': 0.8, 'tech_adoption': 0.7}, 'Львівська': {'avg_age': 38, 'urbanization': 0.7, 'tech_adoption': 0.6}, 'Харківська': {'avg_age': 42, 'urbanization': 0.8, 'tech_adoption': 0.65}, 'Одеська': {'avg_age': 41, 'urbanization': 0.7, 'tech_adoption': 0.6}, 'Дніпропетровська': {'avg_age': 43, 'urbanization': 0.75, 'tech_adoption': 0.6}, 'Запорізька': {'avg_age': 44, 'urbanization': 0.7, 'tech_adoption': 0.55}, 'Вінницька': {'avg_age': 42, 'urbanization': 0.6, 'tech_adoption': 0.5}, 'Полтавська': {'avg_age': 43, 'urbanization': 0.65, 'tech_adoption': 0.55}, 'Чернігівська': {'avg_age': 45, 'urbanization': 0.6, 'tech_adoption': 0.5}, 'Сумська': {'avg_age': 44, 'urbanization': 0.65, 'tech_adoption': 0.5} } # Generate initial customer data data = [] for i in range(NUM_CUSTOMERS): customer_id = f"C{str(i+1).zfill(6)}" # Region and City region = np.random.choice(list(REGIONS.keys())) region_info = REGIONS[region].copy() # Create a copy to avoid modifying the original is_urban = np.random.random() < region_info['urbanization'] city = fake.city() if not is_urban: city = f"смт {city}" # Age (dependent on region) age = int(np.random.normal(region_info['avg_age'], 10)) age_noise = np.random.normal(0, 2) # Add noise with mean 0 and std dev 2 age = max(18, min(80, int(age + age_noise))) # Add noise to urbanization and tech adoption urbanization_noise = np.random.normal(0, 0.05) tech_adoption_noise = np.random.normal(0, 0.05) region_info['urbanization'] = max(0, min(1, region_info['urbanization'] + urbanization_noise)) region_info['tech_adoption'] = max(0, min(1, region_info['tech_adoption'] + tech_adoption_noise)) # Gender (slight dependency on age and region) gender_prob = 0.49 + 0.02 * (age - 40) / 40 # Slight increase in male probability with age gender_prob += 0.02 * (region_info['urbanization'] - 0.7) / 0.3 # Slight increase in urban areas gender = np.random.choice(['Male', 'Female', 'Other'], p=[gender_prob, 1-gender_prob-0.01, 0.01]) # Preferred Language (dependent on age and region) ukrainian_prob = 0.8 - 0.2 * (age - 40) / 40 # Younger people more likely to prefer Ukrainian ukrainian_prob += 0.1 * (1 - region_info['urbanization']) # Rural areas more likely to prefer Ukrainian preferred_language = np.random.choice(['Ukrainian', 'Russian'], p=[min(1, max(0, ukrainian_prob)), 1-min(1, max(0, ukrainian_prob))]) # Registration date registration_date = fake.date_between(start_date=START_DATE, end_date=END_DATE) # Determine if the customer is active (has made orders) is_active = np.random.random() < 0.6 # 60% chance of being an active customer if is_active: # Total orders and average order value (dependent on various factors) base_orders = np.random.poisson(5) order_multiplier = 1 + 0.2 * (age - 40) / 40 # Age factor order_multiplier *= 1 + 0.1 * (region_info['tech_adoption'] - 0.6) / 0.2 # Tech adoption factor order_multiplier *= 1.1 if gender == 'Female' else 0.9 # Gender factor order_multiplier *= 1.1 if preferred_language == 'Ukrainian' else 0.9 # Language factor total_orders = max(1, int(base_orders * order_multiplier)) # Ensure at least 1 order for active customers # Add noise to total orders total_orders_noise = np.random.poisson(2) total_orders = max(1, total_orders + total_orders_noise) base_aov = np.random.gamma(shape=5, scale=100) aov_multiplier = 1 + 0.3 * (age - 40) / 40 # Age factor aov_multiplier *= 1 + 0.2 * (region_info['urbanization'] - 0.7) / 0.3 # Urbanization factor aov_multiplier *= 1.1 if gender == 'Male' else 0.9 # Gender factor average_order_value = base_aov * aov_multiplier # Add noise to average order value aov_noise = np.random.normal(0, average_order_value * 0.1) # 10% noise average_order_value = max(0, average_order_value + aov_noise) # Last order date last_order_date = fake.date_between(start_date=registration_date, end_date=END_DATE) else: total_orders = 0 average_order_value = 0 last_order_date = None # Loyalty level based on total orders loyalty_level = min(5, max(1, int(total_orders / 2))) # Add some randomness to loyalty level loyalty_noise = np.random.randint(-1, 2) # -1, 0, or 1 loyalty_level = max(1, min(5, loyalty_level + loyalty_noise)) # Newsletter subscription (dependent on age, loyalty, and tech adoption) newsletter_prob = 0.5 + 0.1 * loyalty_level / 5 - 0.2 * (age - 40) / 40 + 0.2 * region_info['tech_adoption'] newsletter_noise = np.random.normal(0, 0.1) newsletter_prob = max(0, min(1, newsletter_prob + newsletter_noise)) newsletter_subscription = np.random.random() < newsletter_prob # Preferred payment method (dependent on age and urbanization) payment_probs = [ 0.5 - 0.2 * (age - 40) / 40 + 0.2 * region_info['urbanization'], # Credit Card 0.3 + 0.2 * (age - 40) / 40 - 0.2 * region_info['urbanization'], # Cash on Delivery 0.15, # Bank Transfer 0.05 + 0.1 * region_info['tech_adoption'] # PayPal ] payment_probs = [max(0, min(p, 1)) for p in payment_probs] payment_probs = [p / sum(payment_probs) for p in payment_probs] preferred_payment_method = np.random.choice( ['Credit Card', 'Cash on Delivery', 'Bank Transfer', 'PayPal'], p=payment_probs ) # Add some inconsistency to preferred payment method if np.random.random() < 0.1: # 10% chance of inconsistent preference preferred_payment_method = np.random.choice(['Credit Card', 'Cash on Delivery', 'Bank Transfer', 'PayPal']) # Main browsing device (dependent on age and tech adoption) device_probs = [ 0.4 + 0.3 * (age - 40) / 40 - 0.2 * region_info['tech_adoption'], # Web 0.4 - 0.2 * (age - 40) / 40 + 0.1 * region_info['tech_adoption'], # Mobile 0.2 - 0.1 * (age - 40) / 40 + 0.1 * region_info['tech_adoption'] # App ] device_probs = [max(0, min(p, 1)) for p in device_probs] device_probs = [p / sum(device_probs) for p in device_probs] # Add noise to main browsing device probabilities device_noise = np.random.normal(0, 0.05, size=3) device_probs = [max(0, min(1, p + n)) for p, n in zip(device_probs, device_noise)] device_probs = [p / sum(device_probs) for p in device_probs] main_browsing_device = np.random.choice(['Web', 'Mobile', 'App'], p=device_probs) # Product categories (dependent on age, gender, and browsing device) all_categories = ['Electronics', 'Home Appliances', 'Computers', 'Smartphones', 'TV & Audio'] category_probs = [0.2] * 5 if age < 30: category_probs[2] += 0.1 # Increase Computers category_probs[3] += 0.1 # Increase Smartphones elif age > 60: category_probs[1] += 0.1 # Increase Home Appliances category_probs[4] += 0.1 # Increase TV & Audio if gender == 'Male': category_probs[0] += 0.05 # Slight increase in Electronics category_probs[2] += 0.05 # Slight increase in Computers if main_browsing_device == 'Mobile': category_probs[3] += 0.1 # Increase Smartphones category_probs = [p / sum(category_probs) for p in category_probs] num_categories = np.random.randint(1, 4) product_categories = np.random.choice(all_categories, size=num_categories, replace=False, p=category_probs) data.append({ 'customer_id': customer_id, 'name': fake.name(), 'email': generate_email(fake.name()), 'age': age, 'gender': gender, 'region': region, 'city': city, 'registration_date': registration_date, 'phone_number': generate_phone_number(), 'preferred_language': preferred_language, 'newsletter_subscription': newsletter_subscription, 'preferred_payment_method': preferred_payment_method, 'loyalty_level': loyalty_level, 'main_browsing_device': main_browsing_device, 'product_categories_of_interest': ', '.join(product_categories), 'average_order_value': round(average_order_value, 2), 'total_orders': total_orders, 'last_order_date': last_order_date }) # Create DataFrame df = pd.DataFrame(data) return df if __name__ == "__main__": df = generate_synthetic_data() print(df.head())