[Python] How to improve precipitation prediction when most values in the dataset are zero?

Discussão em 'Python' iniciado por Stack, Maio 2, 2025.

  1. Stack

    Stack Membro Participativo

    I'm working on a precipitation prediction project using time series models like LSTM, ARIMA, and Prophet in Google Colab. The dataset I'm using contains daily precipitation values, but the vast majority of the entries are zero — since on most days, it doesn't rain.

    Because of this extreme imbalance (many zeros and very few rainy days), my models often perform poorly. For instance:

    LSTM tends to output constant or near-zero predictions, likely overfitting to the frequent zero values.

    ARIMA struggles to capture meaningful patterns and ends up producing flat or noisy forecasts.

    Prophet also fails to model the few rainfall spikes correctly and often underestimates any precipitation.

    I want to get better model and predict for example 5 years forecast or I don't know exactly how much of an estimate I can make ?

    How can I better model or preprocess such sparse time series data for precipitation prediction? Are there any recommended techniques to handle the zero-inflated nature of such data in time series forecasting?

    Any advice or shared experience would be appreciated. Thanks in advance!

    Hear is my colab code:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    plt.style.use('fivethirtyeight')
    from sklearn.metrics import mean_squared_error
    import seaborn as sns

    from statsmodels.tsa.arima_model import ARIMA
    from statsmodels.tsa.stattools import adfuller, acf, pacf
    from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

    weather_df = pd.read_csv('sample_data/preprocessed.csv', parse_dates=['DATE'], index_col='DATE')
    weather_df.head()

    def list_and_visualize_missing_data(dataset):
    # Listing total null items and its percent with respect to all nulls
    total = dataset.isnull().sum().sort_values(ascending=False)
    percent = ((dataset.isnull().sum())/(dataset.isnull().count())).sort_values(ascending=False)
    percent = percent*100

    print('Count of missing data : \n',total)
    print('% of missing data : \n',percent)

    list_and_visualize_missing_data(weather_df)


    #check trend and seasonality of weather data
    weather_df.plot(subplots=True, figsize=(20,12))
    #detailed view of 2015 year
    weather_df['2015':'2016'].resample('D')
    weather_df['2015':'2016'].fillna(weather_df['2015':'2016']['PRECTOTCORR'].mean())
    weather_df['2015':'2016'].plot(subplots=True, figsize=(20,12))

    print(weather_df)

    weather_df.index = pd.to_datetime(weather_df.index, format='%Y-%m-%d') # İndeks olarak tarih formatına dönüştür

    precip = weather_df[['PRECTOTCORR']]

    # Tarih indeksine göre sıralı olduğundan emin ol
    precip = precip.sort_index()

    # Veri setinin uzunluğunu al
    n = len(precip)
    train_size = int(n * 0.8)

    # %80 eğitim, %20 test verisi olarak ayır
    train_df = precip.iloc[:train_size]
    test_df = precip.iloc[train_size:]

    train_df.describe()
    print(train_df)

    # check rolling mean and rolling standard deviation
    def plot_rolling_mean_std(ts):
    rolling_mean = ts.rolling(12).mean()
    rolling_std = ts.rolling(12).std()
    plt.figure(figsize=(22,10))

    plt.plot(ts, label='Actual Mean')
    plt.plot(rolling_mean, label='Rolling Mean')
    plt.plot(rolling_std, label = 'Rolling Std')
    plt.xlabel("Date")
    plt.ylabel("Mean Temperature")
    plt.title('Rolling Mean & Rolling Standard Deviation')
    plt.legend()
    plt.show()

    # check stationary: mean, variance(std)and adfuller test
    plot_rolling_mean_std(train_df.PRECTOTCORR)

    #Plot ACF and PACF
    acf_lag = acf(train_df.values, nlags=20)
    pacf_lag = pacf(train_df.values, nlags=20, method='ols')

    plt.figure(figsize=(22,10))

    plt.subplot(1,2,1)
    plt.plot(acf_lag)
    plt.axhline(y=0,linestyle='--',color='silver')
    plt.axhline(y=-1.96/np.sqrt(len(train_df.values)),linestyle='--',color='silver')
    plt.axhline(y=1.96/np.sqrt(len(train_df.values)),linestyle='--',color='silver')
    plt.title("Autocorrelation Function")

    plt.subplot(1,2,2)
    plt.plot(pacf_lag)
    plt.axhline(y=0,linestyle='--',color='silver')
    plt.axhline(y=-1.96/np.sqrt(len(train_df.values)),linestyle='--',color='silver')
    plt.axhline(y=1.96/np.sqrt(len(train_df.values)),linestyle='--',color='silver')
    plt.title("Partial Autocorrelation Function")
    plt.tight_layout()

    #ARIMA model
    from statsmodels.tsa.arima.model import ARIMA

    print("train_df values:",train_df)
    model = ARIMA(train_df.values, order=(2, 0, 2))
    model_fit = model.fit()
    print(model_fit.summary())

    # Plot residual errors
    plt.rcParams.update({'figure.figsize':(12,3), 'figure.dpi':120})
    residuals = pd.DataFrame(model_fit.resid)
    fig, ax = plt.subplots(1,2)
    residuals.plot(title="Residuals", ax=ax[0])
    residuals.plot(kind='kde', title='Density', ax=ax[1])
    plt.show()



    # Actual vs Fitted
    fig, ax = plt.subplots(figsize=(10, 5))
    plot_predict(model_fit, dynamic=False, ax=ax)

    # Başlık ve etiketler
    ax.set_title("Gerçek ve Tahmin Edilen Değerler (Actual vs Fitted)", fontsize=14)
    ax.set_ylabel("Değer", fontsize=12)
    ax.legend(["Gerçek Değer", "Tahmin"], loc="upper left")

    plt.tight_layout()
    plt.show()

    forecast_steps = len(test_df)
    forecast_index = test_df.index

    # 16 dönemlik tahmin ve güven aralığı
    forecast_obj = model_fit.get_forecast(steps=forecast_steps)
    fc = forecast_obj.predicted_mean
    conf = forecast_obj.conf_int(alpha=0.05)

    # Pandas serileri oluştur
    fc_series = pd.Series(fc, index=forecast_index)
    lower_series = pd.Series(conf[:, 0], index=forecast_index)
    upper_series = pd.Series(conf[:, 1], index=forecast_index)

    # Grafik
    plt.figure(figsize=(12,5), dpi=100)
    plt.plot(train_df, label='Test Value')
    plt.plot(test_df, label='Actual Values')
    plt.plot(fc_series, label='Forecast')
    plt.fill_between(lower_series.index, lower_series, upper_series, color='k', alpha=.15)
    plt.title('Forecast vs Actual')
    plt.legend(loc='upper left', fontsize=8)
    plt.show()


    # plot Testing and Forecasted data
    plt.plot(test_df, label='Actual')
    plt.plot(fc_series, label='Forecast', color='red')
    plt.legend(loc='upper left', fontsize=8)
    plt.show()

    error = mean_squared_error(test_df, fc_series)
    print('Test Mean Squared Error: ',error)

    df_result = pd.DataFrame({
    "Actual": test_df["PRECTOTCORR"], # test_df'deki hedef sütunu yaz
    "Forecast": fc_series
    })
    df_result.head(100)


    This is the code github link where I get and adjust for my need:

    https://github.com/Niwedita17/Weather-Forecasting-using-ARIMA

    some screen shots:

    [​IMG]

    [​IMG]

    Continue reading...

Compartilhe esta Página