 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
trend = initial_trend(series, slen)
result.append(series)
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)