diff --git a/src/README.md b/src/README.md index 33340b1..6040964 100644 --- a/src/README.md +++ b/src/README.md @@ -1 +1 @@ -python -m model_train.create_train_dataset --random-seed 42 --num-simulations 100000 --batch-size 10000 --max-workers 7 \ No newline at end of file +python -m olive_oil_train_dataset.create_train_dataset --random-seed 42 --num-simulations 100000 --batch-size 10000 --max-workers 7 diff --git a/src/kaggle/working/models/technique_mapping.joblib b/src/kaggle/working/models/technique_mapping.joblib new file mode 100644 index 0000000..1c8b7ed Binary files /dev/null and b/src/kaggle/working/models/technique_mapping.joblib differ diff --git a/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-39.pyc b/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-39.pyc new file mode 100644 index 0000000..ac54765 Binary files /dev/null and b/src/olive_oil_train_dataset/__pycache__/create_train_dataset.cpython-39.pyc differ diff --git a/src/olive_oil_train_dataset/create_train_dataset.py b/src/olive_oil_train_dataset/create_train_dataset.py index c265fa9..735973f 100644 --- a/src/olive_oil_train_dataset/create_train_dataset.py +++ b/src/olive_oil_train_dataset/create_train_dataset.py @@ -7,6 +7,10 @@ from tqdm import tqdm import os import argparse import sys +import gc +from utils.helpers import clean_column_name, get_growth_phase, calculate_weather_effect, calculate_water_need, \ + create_technique_mapping, preprocess_weather_data + def get_optimal_workers(): """Calcola il numero ottimale di workers basato sulle risorse del sistema""" @@ -26,150 +30,224 @@ def get_optimal_workers(): return max(1, optimal_workers) -def simulate_single_year(params): +def simulate_zone(base_weather, olive_varieties, year, zone, all_varieties, variety_techniques): """ - Simula un singolo anno di produzione. + Simula la produzione di olive per una singola zona. Args: - params: dict contenente: - - weather_annual: dati meteo annuali - - olive_varieties: informazioni sulle varietà - - sim_id: ID simulazione - - random_seed: seed per riproducibilità + base_weather: DataFrame con dati meteo di base per l'anno selezionato + olive_varieties: DataFrame con le informazioni sulle varietà di olive + zone: ID della zona + all_varieties: Array con tutte le varietà disponibili + variety_techniques: Dict con le tecniche disponibili per ogni varietà + + Returns: + Dict con i risultati della simulazione per la zona """ - np.random.seed(params['random_seed'] + params['sim_id']) + # Crea una copia dei dati meteo per questa zona specifica + zone_weather = base_weather.copy() - # Seleziona anno base e applica variazioni - weather = params['weather_annual'].sample(n=1, random_state=params['random_seed'] + params['sim_id']).iloc[0].copy() + # Genera variazioni meteorologiche specifiche per questa zona + zone_weather['temp_mean'] *= np.random.uniform(0.95, 1.05, len(zone_weather)) + zone_weather['precip_sum'] *= np.random.uniform(0.9, 1.1, len(zone_weather)) + zone_weather['solarenergy_sum'] *= np.random.uniform(0.95, 1.05, len(zone_weather)) - # Applica variazioni meteorologiche (±20%) - for col in weather.index: - if col != 'year': - weather[col] *= np.random.uniform(0.8, 1.2) + # Genera caratteristiche specifiche della zona + num_varieties = np.random.randint(1, 4) # 1-3 varietà per zona + selected_varieties = np.random.choice(all_varieties, size=num_varieties, replace=False) + hectares = np.random.uniform(1, 10) # Dimensione del terreno + percentages = np.random.dirichlet(np.ones(num_varieties)) # Distribuzione delle varietà - # Genera caratteristiche dell'oliveto - num_varieties = np.random.randint(1, 4) - selected_varieties = np.random.choice( - params['olive_varieties']['Varietà di Olive'].unique(), - size=num_varieties, - replace=False - ) - hectares = np.random.uniform(1, 10) - percentages = np.random.dirichlet(np.ones(num_varieties)) + # Inizializzazione contatori annuali + annual_production = 0 + annual_min_oil = 0 + annual_max_oil = 0 + annual_avg_oil = 0 + annual_water_need = 0 - annual_results = { - 'simulation_id': params['sim_id'], - 'year': weather['year'], - 'hectares': hectares, - 'num_varieties': num_varieties, - 'total_olive_production': 0, - 'total_oil_production': 0, - 'total_water_need': 0 - } + # Inizializzazione dizionario dati varietà + variety_data = {clean_column_name(variety): { + 'tech': '', + 'pct': 0, + 'prod_t_ha': 0, + 'oil_prod_t_ha': 0, + 'oil_prod_l_ha': 0, + 'min_yield_pct': 0, + 'max_yield_pct': 0, + 'min_oil_prod_l_ha': 0, + 'max_oil_prod_l_ha': 0, + 'avg_oil_prod_l_ha': 0, + 'l_per_t': 0, + 'min_l_per_t': 0, + 'max_l_per_t': 0, + 'avg_l_per_t': 0, + 'olive_prod': 0, + 'min_oil_prod': 0, + 'max_oil_prod': 0, + 'avg_oil_prod': 0, + 'water_need': 0 + } for variety in all_varieties} - # Aggiungi dati meteorologici - for col in weather.index: - if col != 'year': - annual_results[f'weather_{col}'] = weather[col] - - variety_details = [] + # Simula produzione per ogni varietà selezionata for i, variety in enumerate(selected_varieties): - variety_data = params['olive_varieties'][ - params['olive_varieties']['Varietà di Olive'] == variety - ] - technique = np.random.choice(variety_data['Tecnica di Coltivazione'].unique()) + # Seleziona tecnica di coltivazione casuale per questa varietà + technique = np.random.choice(variety_techniques[variety]) percentage = percentages[i] - variety_info = variety_data[ - variety_data['Tecnica di Coltivazione'] == technique + # Ottieni informazioni specifiche della varietà + variety_info = olive_varieties[ + (olive_varieties['Varietà di Olive'] == variety) & + (olive_varieties['Tecnica di Coltivazione'] == technique) ].iloc[0] - # Calcoli produzione con variabilità - production_data = calculate_production( - variety_info, weather, percentage, hectares, - params['sim_id'] + i + # Calcola produzione base con variabilità + base_production = variety_info['Produzione (tonnellate/ettaro)'] * 1000 * percentage * hectares / 12 + base_production *= np.random.uniform(0.9, 1.1) + + # Calcola effetti meteo sulla produzione + weather_effect = zone_weather.apply( + lambda row: calculate_weather_effect(row, variety_info['Temperatura Ottimale']), + axis=1 ) + monthly_production = base_production * (1 + weather_effect / 10000) + monthly_production *= np.random.uniform(0.95, 1.05, len(zone_weather)) - variety_details.append(production_data) + # Calcola produzione annuale per questa varietà + annual_variety_production = monthly_production.sum() - # Aggiorna totali - annual_results['total_olive_production'] += production_data['production'] - annual_results['total_oil_production'] += production_data['oil_production'] - annual_results['total_water_need'] += production_data['water_need'] + # Calcola rese di olio con variabilità + min_yield_factor = np.random.uniform(0.95, 1.05) + max_yield_factor = np.random.uniform(0.95, 1.05) + avg_yield_factor = (min_yield_factor + max_yield_factor) / 2 - # Aggiungi dettagli varietà - for i, detail in enumerate(variety_details): - prefix = f'variety_{i + 1}' - for key, value in detail.items(): - annual_results[f'{prefix}_{key}'] = value + min_oil_production = annual_variety_production * variety_info[ + 'Min Litri per Tonnellata'] / 1000 * min_yield_factor + max_oil_production = annual_variety_production * variety_info[ + 'Max Litri per Tonnellata'] / 1000 * max_yield_factor + avg_oil_production = annual_variety_production * variety_info[ + 'Media Litri per Tonnellata'] / 1000 * avg_yield_factor - # Calcola metriche per ettaro e KPI - annual_results['olive_production_ha'] = annual_results['total_olive_production'] / hectares - annual_results['oil_production_ha'] = annual_results['total_oil_production'] / hectares - annual_results['water_need_ha'] = annual_results['total_water_need'] / hectares + # Calcola fabbisogno idrico + base_water_need = ( + variety_info['Fabbisogno Acqua Primavera (m³/ettaro)'] + + variety_info['Fabbisogno Acqua Estate (m³/ettaro)'] + + variety_info['Fabbisogno Acqua Autunno (m³/ettaro)'] + + variety_info['Fabbisogno Acqua Inverno (m³/ettaro)'] + ) / 4 - # Calcola efficienze - if annual_results['total_olive_production'] > 0: - annual_results['yield_efficiency'] = annual_results['total_oil_production'] / annual_results[ - 'total_olive_production'] - else: - annual_results['yield_efficiency'] = 0 + monthly_water_need = zone_weather.apply( + lambda row: calculate_water_need(row, base_water_need, variety_info['Temperatura Ottimale']), + axis=1 + ) + monthly_water_need *= np.random.uniform(0.95, 1.05, len(monthly_water_need)) + annual_variety_water_need = monthly_water_need.sum() * percentage * hectares - if annual_results['total_water_need'] > 0: - annual_results['water_efficiency'] = annual_results['total_olive_production'] / annual_results[ - 'total_water_need'] - else: - annual_results['water_efficiency'] = 0 + # Aggiorna totali annuali + annual_production += annual_variety_production + annual_min_oil += min_oil_production + annual_max_oil += max_oil_production + annual_avg_oil += avg_oil_production + annual_water_need += annual_variety_water_need - return annual_results + # Aggiorna dati varietà + clean_variety = clean_column_name(variety) + variety_data[clean_variety].update({ + 'tech': clean_column_name(technique), + 'pct': percentage, + 'prod_t_ha': variety_info['Produzione (tonnellate/ettaro)'] * np.random.uniform(0.95, 1.05), + 'oil_prod_t_ha': variety_info['Produzione Olio (tonnellate/ettaro)'] * np.random.uniform(0.95, 1.05), + 'oil_prod_l_ha': variety_info['Produzione Olio (litri/ettaro)'] * np.random.uniform(0.95, 1.05), + 'min_yield_pct': variety_info['Min % Resa'] * min_yield_factor, + 'max_yield_pct': variety_info['Max % Resa'] * max_yield_factor, + 'min_oil_prod_l_ha': variety_info['Min Produzione Olio (litri/ettaro)'] * min_yield_factor, + 'max_oil_prod_l_ha': variety_info['Max Produzione Olio (litri/ettaro)'] * max_yield_factor, + 'avg_oil_prod_l_ha': variety_info['Media Produzione Olio (litri/ettaro)'] * avg_yield_factor, + 'l_per_t': variety_info['Litri per Tonnellata'] * np.random.uniform(0.98, 1.02), + 'min_l_per_t': variety_info['Min Litri per Tonnellata'] * min_yield_factor, + 'max_l_per_t': variety_info['Max Litri per Tonnellata'] * max_yield_factor, + 'avg_l_per_t': variety_info['Media Litri per Tonnellata'] * avg_yield_factor, + 'olive_prod': annual_variety_production, + 'min_oil_prod': min_oil_production, + 'max_oil_prod': max_oil_production, + 'avg_oil_prod': avg_oil_production, + 'water_need': annual_variety_water_need + }) + + # Appiattisci i dati delle varietà + flattened_variety_data = { + f'{variety}_{key}': value + for variety, data in variety_data.items() + for key, value in data.items() + } + + # Restituisci il risultato della zona + return { + 'year': year, + 'zone_id': zone + 1, + 'temp_mean': zone_weather['temp_mean'].mean(), + 'precip_sum': zone_weather['precip_sum'].sum(), + 'solar_energy_sum': zone_weather['solarenergy_sum'].sum(), + 'ha': hectares, + 'zone': f"zone_{zone + 1}", + 'olive_prod': annual_production, + 'min_oil_prod': annual_min_oil, + 'max_oil_prod': annual_max_oil, + 'avg_oil_prod': annual_avg_oil, + 'total_water_need': annual_water_need, + **flattened_variety_data + } -def generate_training_dataset_parallel(weather_data, olive_varieties, num_simulations=1000, - random_seed=42, max_workers=None, batch_size=500, - output_path='olive_training_dataset.parquet'): +def simulate_olive_production_parallel(weather_data, olive_varieties, num_simulations=5, num_zones=None, + random_seed=None, + max_workers=None, batch_size=500, + output_path='olive_simulation_dataset.parquet'): """ - Genera dataset di training utilizzando multiprocessing. + Versione corretta della simulazione parallelizzata con gestione batch e salvataggio file Args: - weather_data: DataFrame dati meteo - olive_varieties: DataFrame varietà olive - num_simulations: numero di simulazioni - random_seed: seed per riproducibilità - max_workers: numero massimo di workers - batch_size: dimensione batch - output_path: percorso file output + weather_data: DataFrame con dati meteo + olive_varieties: DataFrame con varietà di olive + num_simulations: numero di simulazioni da eseguire (default: 5) + num_zones: numero di zone per simulazione (default: None, usa num_simulations se non specificato) + random_seed: seed per riproducibilità (default: None) + max_workers: numero massimo di workers (default: None, usa get_optimal_workers) + batch_size: dimensione del batch per gestione memoria (default: 500) + output_path: percorso del file di output (default: 'olive_simulation_dataset.parquet') + + Returns: + DataFrame con i risultati delle simulazioni """ - np.random.seed(random_seed) + if random_seed is not None: + np.random.seed(random_seed) - # Prepara dati meteo annuali - weather_annual = weather_data.groupby('year').agg({ - 'temp': ['mean', 'min', 'max', 'std'], - 'humidity': ['mean', 'min', 'max'], - 'precip': ['sum', 'mean', 'std'], - 'solarradiation': ['mean', 'sum', 'std'], - 'cloudcover': ['mean'] - }).reset_index() + # Se num_zones non è specificato, usa num_simulations + if num_zones is None: + num_zones = num_simulations - weather_annual.columns = ['year'] + [ - f'{col[0]}_{col[1]}' for col in weather_annual.columns[1:] - ] + # Preparazione dati + create_technique_mapping(olive_varieties) + monthly_weather = preprocess_weather_data(weather_data) + all_varieties = olive_varieties['Varietà di Olive'].unique() + variety_techniques = { + variety: olive_varieties[olive_varieties['Varietà di Olive'] == variety]['Tecnica di Coltivazione'].unique() + for variety in all_varieties + } - # Calcola workers ottimali + # Calcolo workers ottimali usando get_optimal_workers if max_workers is None: max_workers = get_optimal_workers() + print(f"Utilizzando {max_workers} workers ottimali basati sulle risorse del sistema") - print(f"Utilizzando {max_workers} workers") - - # Calcola numero di batch + # Calcolo numero di batch num_batches = (num_simulations + batch_size - 1) // batch_size - print(f"Elaborazione di {num_simulations} simulazioni in {num_batches} batch") - - # Crea directory output se necessario - os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else '.', exist_ok=True) + print(f"Elaborazione di {num_simulations} simulazioni con {num_zones} zone in {num_batches} batch") + print(f"Totale record attesi: {num_simulations * num_zones:,}") # Lista per contenere tutti i DataFrame dei batch all_batches = [] + # Elaborazione per batch for batch_num in range(num_batches): start_sim = batch_num * batch_size end_sim = min((batch_num + 1) * batch_size, num_simulations) @@ -177,54 +255,79 @@ def generate_training_dataset_parallel(weather_data, olive_varieties, num_simula batch_results = [] - # Preparazione parametri per ogni simulazione - simulation_params = [ - { - 'weather_annual': weather_annual, - 'olive_varieties': olive_varieties, - 'sim_id': sim_id, - 'random_seed': random_seed - } - for sim_id in range(start_sim, end_sim) - ] - - # Esegui simulazioni in parallelo + # Parallelizzazione usando ProcessPoolExecutor per il batch corrente with ProcessPoolExecutor(max_workers=max_workers) as executor: - futures = [executor.submit(simulate_single_year, params) - for params in simulation_params] + # Calcola il numero totale di task per questo batch + # Ogni simulazione nel batch corrente genererà num_zones zone + total_tasks = current_batch_size * num_zones - with tqdm(total=current_batch_size, + with tqdm(total=total_tasks, desc=f"Batch {batch_num + 1}/{num_batches}") as pbar: - for future in as_completed(futures): + # Dizionario per tenere traccia delle futures e dei loro sim_id + future_to_sim_id = {} + + # Sottometti i lavori per tutte le simulazioni e zone nel batch corrente + for sim in range(start_sim, end_sim): + selected_year = np.random.choice(monthly_weather['year'].unique()) + base_weather = monthly_weather[monthly_weather['year'] == selected_year].copy() + base_weather.loc[:, 'growth_phase'] = base_weather['month'].apply(get_growth_phase) + + # Sottometti i lavori per tutte le zone di questa simulazione + for zone in range(num_zones): + future = executor.submit( + simulate_zone, + base_weather=base_weather, + olive_varieties=olive_varieties, + year=selected_year, + zone=zone, + all_varieties=all_varieties, + variety_techniques=variety_techniques + ) + future_to_sim_id[future] = (sim + 1, zone + 1) + + # Raccogli i risultati man mano che vengono completati + for future in as_completed(future_to_sim_id.keys()): + sim_id, zone_id = future_to_sim_id[future] try: result = future.result() + result['simulation_id'] = sim_id + result['zone_id'] = zone_id batch_results.append(result) pbar.update(1) except Exception as e: - print(f"Errore in simulazione: {str(e)}") + print(f"Errore nella simulazione {sim_id}, zona {zone_id}: {str(e)}") continue - # Converti risultati in DataFrame + # Converti batch_results in DataFrame e aggiungi alla lista dei batch batch_df = pd.DataFrame(batch_results) all_batches.append(batch_df) + # Stampa statistiche del batch + print(f"\nStatistiche Batch {batch_num + 1}:") + print(f"Righe processate: {len(batch_df):,}") + print(f"Memoria utilizzata: {batch_df.memory_usage(deep=True).sum() / 1024 ** 2:.2f} MB") + # Libera memoria del batch_results + del batch_df + gc.collect() # Forza garbage collection # Concatena tutti i batch e salva + print("\nConcatenazione dei batch e salvataggio...") final_df = pd.concat(all_batches, ignore_index=True) + + # Crea directory output se necessario + os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else '.', exist_ok=True) + + # Salva il dataset final_df.to_parquet(output_path) + # Stampa statistiche finali + print("\nStatistiche Finali:") + print(f"Totale simulazioni completate: {len(final_df):,}") + print(f"Memoria totale utilizzata: {final_df.memory_usage(deep=True).sum() / 1024 ** 2:.2f} MB") print(f"\nDataset salvato in: {output_path}") - # Statistiche finali - print("\nStatistiche finali:") - print(f"Righe totali: {len(final_df)}") - print("\nAnalisi variabilità:") - for col in ['olive_production_ha', 'oil_production_ha', 'water_need_ha']: - cv = final_df[col].std() / final_df[col].mean() - print(f"{col}: CV = {cv:.2%}") - return final_df @@ -319,7 +422,6 @@ def calculate_solar_effect(radiation): return base_factor * np.random.uniform(0.8, 1.2) - def parse_arguments(): """ Configura e gestisce i parametri da riga di comando @@ -339,10 +441,17 @@ def parse_arguments(): parser.add_argument( '--num-simulations', type=int, - default=1000000, + default=100000, help='Numero totale di simulazioni da eseguire' ) + parser.add_argument( + '--num-zones', + type=int, + default=None, + help='Numero di zone per simulazione (default: uguale a num-simulations)' + ) + parser.add_argument( '--batch-size', type=int, @@ -360,25 +469,21 @@ def parse_arguments(): parser.add_argument( '--max-workers', type=int, - default=2, - help='Quantità di workers' + default=None, + help='Quantità di workers (default: usa get_optimal_workers)' ) return parser.parse_args() -# Esempio di utilizzo if __name__ == "__main__": print("Generazione dataset di training...") - - # Parsing argomenti args = parse_arguments() # Carica dati try: - # Carica dati weather_data = pd.read_parquet('./sources/weather_data_complete.parquet') olive_varieties = pd.read_parquet('./sources/olive_varieties.parquet') except Exception as e: @@ -389,36 +494,23 @@ if __name__ == "__main__": print("\nConfigurazione:") print(f"Random seed: {args.random_seed}") print(f"Numero simulazioni: {args.num_simulations:,}") - print(f"Workers: {args.max_workers:,}") + print(f"Numero zone per simulazione: {args.num_zones if args.num_zones is not None else args.num_simulations:,}") + print(f"Workers: {args.max_workers if args.max_workers is not None else 'auto'}") print(f"Dimensione batch: {args.batch_size:,}") print(f"File output: {args.output_path}") # Genera dataset try: - df = generate_training_dataset_parallel( + df = simulate_olive_production_parallel( weather_data=weather_data, olive_varieties=olive_varieties, - random_seed=args.random_seed, num_simulations=args.num_simulations, + num_zones=args.num_zones, + random_seed=args.random_seed, batch_size=args.batch_size, output_path=args.output_path, max_workers=args.max_workers ) except Exception as e: print(f"Errore durante la generazione del dataset: {str(e)}") - sys.exit(1) - - print("\nShape dataset:", df.shape) - print("\nColonne disponibili:") - print(df.columns.tolist()) - - print("\nStatistiche di base:") - print(df.describe()) - - # Analisi variabilità - print("\nAnalisi coefficienti di variazione:") - for col in ['olive_production_ha', 'oil_production_ha', 'water_need_ha']: - cv = df[col].std() / df[col].mean() - print(f"{col}: {cv:.2%}") - - print("\nDataset salvato './sources/olive_training_dataset.parquet'") \ No newline at end of file + sys.exit(1) \ No newline at end of file diff --git a/src/utils/__pycache__/__init__.cpython-39.pyc b/src/utils/__pycache__/__init__.cpython-39.pyc index 7c5c2b8..f493464 100644 Binary files a/src/utils/__pycache__/__init__.cpython-39.pyc and b/src/utils/__pycache__/__init__.cpython-39.pyc differ diff --git a/src/utils/__pycache__/helpers.cpython-39.pyc b/src/utils/__pycache__/helpers.cpython-39.pyc index 1e7d28d..597d4c0 100644 Binary files a/src/utils/__pycache__/helpers.cpython-39.pyc and b/src/utils/__pycache__/helpers.cpython-39.pyc differ diff --git a/src/utils/helpers.py b/src/utils/helpers.py index 915718b..b408295 100644 --- a/src/utils/helpers.py +++ b/src/utils/helpers.py @@ -2,8 +2,10 @@ import psutil import multiprocessing import re import pandas as pd -from typing import List, Dict import numpy as np +from typing import List, Dict +import os +import joblib def get_optimal_workers() -> int: @@ -215,12 +217,6 @@ def get_full_data(simulated_data: pd.DataFrame, return full_data - - - -import numpy as np -from typing import List, Dict - def prepare_static_features_multiple(varieties_info: List[Dict], percentages: List[float], hectares: float, @@ -376,4 +372,133 @@ def add_controlled_variation(base_value: float, max_variation_pct: float = 0.20) Valore con variazione applicata """ variation = np.random.uniform(-max_variation_pct, max_variation_pct) - return base_value * (1 + variation) \ No newline at end of file + return base_value * (1 + variation) + +def get_growth_phase(month): + if month in [12, 1, 2]: + return 'dormancy' + elif month in [3, 4, 5]: + return 'flowering' + elif month in [6, 7, 8]: + return 'fruit_set' + else: + return 'ripening' + +def calculate_weather_effect(row, optimal_temp): + # Effetti base + temp_effect = -0.1 * (row['temp_mean'] - optimal_temp) ** 2 + rain_effect = -0.05 * (row['precip_sum'] - 600) ** 2 / 10000 + sun_effect = 0.1 * row['solarenergy_sum'] / 1000 + + # Fattori di scala basati sulla fase di crescita + if row['growth_phase'] == 'dormancy': + temp_scale = 0.5 + rain_scale = 0.2 + sun_scale = 0.1 + elif row['growth_phase'] == 'flowering': + temp_scale = 2.0 + rain_scale = 1.5 + sun_scale = 1.0 + elif row['growth_phase'] == 'fruit_set': + temp_scale = 1.5 + rain_scale = 1.0 + sun_scale = 0.8 + else: # ripening + temp_scale = 1.0 + rain_scale = 0.5 + sun_scale = 1.2 + + # Calcolo dell'effetto combinato + combined_effect = ( + temp_scale * temp_effect + + rain_scale * rain_effect + + sun_scale * sun_effect + ) + + # Aggiustamenti specifici per fase + if row['growth_phase'] == 'flowering': + combined_effect -= 0.5 * max(0, row['precip_sum'] - 50) # Penalità per pioggia eccessiva durante la fioritura + elif row['growth_phase'] == 'fruit_set': + combined_effect += 0.3 * max(0, row['temp_mean'] - (optimal_temp + 5)) # Bonus per temperature più alte durante la formazione dei frutti + + return combined_effect + +def calculate_water_need(weather_data, base_need, optimal_temp): + # Calcola il fabbisogno idrico basato su temperatura e precipitazioni + temp_factor = 1 + 0.05 * (weather_data['temp_mean'] - optimal_temp) # Aumenta del 5% per ogni grado sopra l'ottimale + rain_factor = 1 - 0.001 * weather_data['precip_sum'] # Diminuisce leggermente con l'aumentare delle precipitazioni + return base_need * temp_factor * rain_factor + +def create_technique_mapping(olive_varieties, mapping_path='./kaggle/working/models/technique_mapping.joblib'): + # Estrai tutte le tecniche uniche dal dataset e convertile in lowercase + all_techniques = olive_varieties['Tecnica di Coltivazione'].str.lower().unique() + + # Crea il mapping partendo da 1 + technique_mapping = {tech: i + 1 for i, tech in enumerate(sorted(all_techniques))} + + # Salva il mapping + os.makedirs(os.path.dirname(mapping_path), exist_ok=True) + joblib.dump(technique_mapping, mapping_path) + + return technique_mapping + + +def encode_techniques(df, mapping_path='./kaggle/working/models/technique_mapping.joblib'): + if not os.path.exists(mapping_path): + raise FileNotFoundError(f"Mapping not found at {mapping_path}. Run create_technique_mapping first.") + + technique_mapping = joblib.load(mapping_path) + + # Trova tutte le colonne delle tecniche + tech_columns = [col for col in df.columns if col.endswith('_tech')] + + # Applica il mapping a tutte le colonne delle tecniche + for col in tech_columns: + df[col] = df[col].str.lower().map(technique_mapping).fillna(0).astype(int) + + return df + + +def decode_techniques(df, mapping_path='./kaggle/working/models/technique_mapping.joblib'): + if not os.path.exists(mapping_path): + raise FileNotFoundError(f"Mapping not found at {mapping_path}") + + technique_mapping = joblib.load(mapping_path) + reverse_mapping = {v: k for k, v in technique_mapping.items()} + reverse_mapping[0] = '' # Aggiungi un mapping per 0 a stringa vuota + + # Trova tutte le colonne delle tecniche + tech_columns = [col for col in df.columns if col.endswith('_tech')] + + # Applica il reverse mapping a tutte le colonne delle tecniche + for col in tech_columns: + df[col] = df[col].map(reverse_mapping) + + return df + + +def decode_single_technique(technique_value, mapping_path='./kaggle/working/models/technique_mapping.joblib'): + if not os.path.exists(mapping_path): + raise FileNotFoundError(f"Mapping not found at {mapping_path}") + + technique_mapping = joblib.load(mapping_path) + reverse_mapping = {v: k for k, v in technique_mapping.items()} + reverse_mapping[0] = '' + + return reverse_mapping.get(technique_value, '') + +def preprocess_weather_data(weather_df): + # Calcola statistiche mensili per ogni anno + monthly_weather = weather_df.groupby(['year', 'month']).agg({ + 'temp': ['mean', 'min', 'max'], + 'humidity': 'mean', + 'precip': 'sum', + 'windspeed': 'mean', + 'cloudcover': 'mean', + 'solarradiation': 'sum', + 'solarenergy': 'sum', + 'uvindex': 'max' + }).reset_index() + + monthly_weather.columns = ['year', 'month'] + [f'{col[0]}_{col[1]}' for col in monthly_weather.columns[2:]] + return monthly_weather \ No newline at end of file