Results of Julia code different slightly than equivalent Python code

I’m converting a Python code to Julia, I’m newbie to both in real, and got slight different in the output, which could be related to the indexes difference between the 2 languages but not sure:

Python code is:

def triple_exponential_smoothing(series, slen, alpha, beta, gamma, n_preds):
    result = []
    seasonals = initial_seasonal_components(series, slen)
    for i in range(len(series)+n_preds):
        if i == 0: # initial values
            smooth = series[0]
            trend = initial_trend(series, slen)
            result.append(series[0])
            continue
        if i >= len(series): # we are forecasting
            m = i - len(series) + 1
            result.append((smooth + m*trend) + seasonals[i%slen])
        else:
            val = series[i]
            last_smooth, smooth = smooth, alpha*(val-seasonals[i%slen]) + (1-alpha)*(smooth+trend)
            trend = beta * (smooth-last_smooth) + (1-beta)*trend
            seasonals[i%slen] = gamma*(val-smooth) + (1-gamma)*seasonals[i%slen]
            result.append(smooth+trend+seasonals[i%slen])
    return result

Equivalent Julia code is:

function triple_exponential_smoothing(series, slen, α, β, γ, n_preds)
    result = []
    smooth = 0.0
    trend = 0.0
    seasonals = initial_seasonal_components(series, slen)
    println("The seasonalities are: $seasonals")
    for i in (1:length(series) + n_preds + 1)
        if i == 1 # iniial value
                smooth = series[i]
                trend = initial_trend(series, slen)
                println("The initial_trend is: $trend")
                push!(result, series[i])
        elseif i >= length(series) # we are forecasting
                m = i - length(series) + 1
                push!(result, (smooth + m * trend) + seasonals[i % slen + 1])
        else  # we are simulating history
                val = series[i]
                last_smooth = smooth
                smooth = α * (val - seasonals[i % slen + 1]) +
                    (1.0 - α)*(smooth + trend)
                trend = β * (smooth - last_smooth) + (1.0 - β) * trend
                seasonals[i % slen + 1] = γ * (val - smooth) +
                    (1.0 - γ) * seasonals[i % slen + 1]
                push!(result, smooth + trend + seasonals[i % slen + 1])
        end
    end
    println("The forecast is:")
    result
    return result
end

Output of Python is:

The forecast is: [
    30.0,
    20.344492,
    28.410053,
    30.438124,
    39.46682,
    ⋮   
    41.15883,
    31.517647,
    33.275066,
    28.828945,
    32.61863,
]

The output of Juia is:

97-element Array{Any,1}:
 30                
 20.1974145        
 28.31508149221089 
 30.25227323256118 
 39.31840215208643 
 ⋮   
 30.541983724030448
 31.02710824538603 
 26.624311736837914
 29.164185056307655
 19.070532032623255

My full code, to help testing, is:

initial_trend(series, slen) = sum(
    map(i -> (last(i) - first(i)) / slen, 
        zip(series[1:slen], series[slen+1:2*slen])
        )
    ) / slen

function initial_seasonal_components(series, slen)
season_averages = map(i -> sum(i) / length(i) ,Iterators.partition(series,12) |> collect)
return map(i -> begin
            sum_of_vals_over_avg = 0.0
            map(j -> # for j in (0:6-1)
                sum_of_vals_over_avg += series[i + j * 12] - season_averages[j+1]
            , (0:6-1)) #end
            sum_of_vals_over_avg / 6
        end
      , (1:12))
end

function triple_exponential_smoothing(series, slen, α, β, γ, n_preds)
    result = []
    smooth = 0.0
    trend = 0.0
    seasonals = initial_seasonal_components(series, slen)
    println("The seasonalities are: $seasonals")
    for i in (1:length(series) + n_preds + 1)
        if i == 1 # iniial value
                smooth = series[i]
                trend = initial_trend(series, slen)
                println("The initial_trend is: $trend")
                push!(result, series[i])
        elseif i >= length(series) # we are forecasting
                m = i - length(series) + 1
                push!(result, (smooth + m * trend) + seasonals[i % slen + 1])
        else  # we are simulating history
                val = series[i]
                last_smooth = smooth
                smooth = α * (val - seasonals[i % slen + 1]) +
                    (1.0 - α)*(smooth + trend)
                trend = β * (smooth - last_smooth) + (1.0 - β) * trend
                seasonals[i % slen + 1] = γ * (val - smooth) +
                    (1.0 - γ) * seasonals[i % slen + 1]
                push!(result, smooth + trend + seasonals[i % slen + 1])
        end
    end
    println("The forecast is:")
    result
    return result
end

series = [30,21,29,31,40,48,53,47,37,39,31,29,17,9,20,24,27,35,41,38,
          27,31,27,26,21,13,21,18,33,35,40,36,22,24,21,20,17,14,17,19,
          26,29,40,31,20,24,18,26,17,9,17,21,28,32,46,33,23,28,22,27,
          18,8,17,21,31,34,44,38,31,30,26,32];

triple_exponential_smoothing(series, 12, 0.716, 0.029, 0.993, 24)

elseif i >= length(series)

This seems wrong, given the offsetting you’re doing in the for loop header.

You might want to add code that calls the function with dummy inputs, so that it’s easier for people to test.

That should really be

result = Float64[]

for performance reasons.

I can’t run your code, so there isn’t much I can say. I also haven’t looked over it thoroughly, but with a quick glance I noticed that the Python says

for i in range(len(series)+n_preds):
....
        if i >= len(series): # we are forecasting
            m = i - len(series) + 1

While the Julia says

    for i in (1:length(series) + n_preds + 1)
...
        elseif i >= length(series) # we are forecasting
                m = i - length(series) + 1

Seems the Julia should instead say m = i - length(series). (EDIT: and tkoolen pointed out that the i >= should be fixed too.)
I’d make sure you translated all the zero and one based indices correctly.

I added the full code to the end of the question

no changes in real, I changed:

result = Float64[]

elseif i > length(series) # we are forecasting
                m = i - length(series)

But got no impact on the result.

I added the full code to the question if it could help testing

julia> initial_trend(series, slen) = sum(
           map(i -> (last(i) - first(i)) / slen, 
               zip(series[1:slen], series[slen+1:2*slen])
               )
           ) / slen
initial_trend (generic function with 1 method)

julia> function initial_seasonal_components(series, slen)
       season_averages = map(i -> sum(i) / length(i) ,Iterators.partition(series,12) |> collect)
       return map(i -> begin
                   sum_of_vals_over_avg = 0.0
                   map(j -> # for j in (0:6-1)
                       sum_of_vals_over_avg += series[i + j * 12] - season_averages[j+1]
                   , (0:6-1)) #end
                   sum_of_vals_over_avg / 6
               end
             , (1:12))
       end
initial_seasonal_components (generic function with 1 method)

julia> function triple_exponential_smoothing(series, slen, α, β, γ, n_preds)
           result = Float64[]
           smooth = 0.0
           trend = 0.0
           seasonals = initial_seasonal_components(series, slen)
           println("The seasonalities are: $seasonals")
           for i in (0:length(series) + n_preds - 1)
               if i == 0 # iniial value
                       smooth = series[i+1]
                       trend = initial_trend(series, slen)
                       println("The initial_trend is: $trend")
                       push!(result, series[i+1])
               elseif i >= length(series) # we are forecasting
                       m = i - length(series) + 1
                       push!(result, (smooth + m * trend) + seasonals[i % slen + 1])
               else  # we are simulating history
                       val = series[i+1]
                       last_smooth = smooth
                       smooth = α * (val - seasonals[i % slen + 1]) +
                           (1.0 - α)*(smooth + trend)
                       trend = β * (smooth - last_smooth) + (1.0 - β) * trend
                       seasonals[i % slen + 1] = γ * (val - smooth) +
                           (1.0 - γ) * seasonals[i % slen + 1]
                       push!(result, smooth + trend + seasonals[i % slen + 1])
               end
           end
           println("The forecast is:")
           result
           return result
       end
triple_exponential_smoothing (generic function with 1 method)

julia> series = [30,21,29,31,40,48,53,47,37,39,31,29,17,9,20,24,27,35,41,38,
                 27,31,27,26,21,13,21,18,33,35,40,36,22,24,21,20,17,14,17,19,
                 26,29,40,31,20,24,18,26,17,9,17,21,28,32,46,33,23,28,22,27,
                 18,8,17,21,31,34,44,38,31,30,26,32];

julia> triple_exponential_smoothing(series, 12, 0.716, 0.029, 0.993, 24)
The seasonalities are: [-7.4305555555555545, -15.097222222222221, -7.263888888888888, -5.097222222222222, 3.402777777777778, 8.069444444444445, 16.569444444444446, 9.736111111111112, -0.7638888888888887, 1.902777777777778, -3.263888888888889, -0.7638888888888887]
The initial_trend is: -0.7847222222222222
The forecast is:
96-element Array{Float64,1}:
 30.0
 20.34449316666667
 28.410051892109554
 30.438122252647577
 39.466817731253066
 47.54961891047195
 52.52339682497974
 46.53453460769274
 36.558407328055765
 38.56283307754578
 30.51864332437879
 28.425963657825292
 16.30247725646635
  8.228588857142476
 19.30036874234319
 23.38657154193773
 26.323990741396006
 34.356648660113095
 40.36971459184453
 37.44298129818558
 26.469996240541015
 30.51819842804787
 26.580158132275145
 25.556750355604414
 20.59232938487544
 12.557525846506284
 20.536167580315634
 17.449559582909338
 32.589947392978274
 34.559067611499714
 39.524706984702796
 35.54354494552727
 21.507741573047714
 23.48782855767762
 20.541994359470845
  ⋮
  7.79136434612686
 16.79511449881349
 20.831653319362697
 30.885227379775543
 33.87620406969448
 43.8722204956629
 37.93866311702782
 31.017079798498486
 29.952760178336057
 25.95873287479028
 32.01973275816115
 22.42511411230803
 15.343371755223066
 24.14282581581347
 27.02259921391996
 35.31139046245393
 38.999014669337356
 49.243283875692654
 40.84636009563803
 31.205180503707012
 32.96259980122959
 28.5164783238384
 32.30616336737171
 22.737583867810464
 15.655841510725496
 24.4552955713159
 27.33506896942239
 35.62386021795636
 39.31148442483978
 49.55575363119508
 41.15882985114047
 31.517650259209443
 33.275069556732014
 28.82894807934083
 32.618633122874144

Thanks, so the main mistake I’d done is

for i in range(len(series)+n_preds):

To

for i in (1:length(series) + n_preds + 1)

Instead of:

for i in (0:length(series) + n_preds - 1)

And accordingly changing series[i] to series[i+1]