@@ -78,8 +78,8 @@ def _downsample(
78
78
79
79
# Construct the output array
80
80
sampled_x = np .empty (n_out , dtype = "int64" )
81
+ # Add the first point
81
82
sampled_x [0 ] = 0
82
- sampled_x [- 1 ] = x .shape [0 ] - 1
83
83
84
84
# Convert x & y to int if it is boolean
85
85
if x .dtype == np .bool_ :
@@ -93,7 +93,17 @@ def _downsample(
93
93
LTTB_py ._argmax_area (
94
94
prev_x = x [a ],
95
95
prev_y = y [a ],
96
- avg_next_x = np .mean (x [offset [i + 1 ] : offset [i + 2 ]]),
96
+ # NOTE: In a 100% correct implementation of LTTB the next x average
97
+ # should be implemented as the following:
98
+ # avg_next_x=np.mean(x[offset[i + 1] : offset[i + 2]]),
99
+ # To improve performance we use the following approximation
100
+ # which is the average of the first and last point of the next bucket
101
+ # NOTE: this is not as accurate when x is not sampled equidistant
102
+ # or when the buckets do not contain tht much data points, but it:
103
+ # (1) aligns with visual perception (visual middle)
104
+ # (2) is much faster
105
+ # (3) is how the LTTB rust implementation works
106
+ avg_next_x = (x [offset [i + 1 ]] + x [offset [i + 2 ] - 1 ]) / 2.0 ,
97
107
avg_next_y = y [offset [i + 1 ] : offset [i + 2 ]].mean (),
98
108
x_bucket = x [offset [i ] : offset [i + 1 ]],
99
109
y_bucket = y [offset [i ] : offset [i + 1 ]],
@@ -115,6 +125,8 @@ def _downsample(
115
125
)
116
126
+ offset [- 2 ]
117
127
)
128
+ # Always include the last point
129
+ sampled_x [- 1 ] = x .shape [0 ] - 1
118
130
return sampled_x
119
131
120
132
0 commit comments