Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
245c241
:bangbang: add own lttbc implementation
jonasvdd Jun 25, 2022
f8b3ca4
:lock:
jonasvdd Jun 27, 2022
cf25961
:sparkles: adding lttb return index method
jonasvdd Jun 27, 2022
fa7cba8
:level_slider: add windows & mac-os to test matrix
jvdd Jun 27, 2022
cc951c1
:fire: :goal_net: :dart: add MIT license
jonasvdd Jun 28, 2022
99da003
:see_no_evil: fix for #89
jonasvdd Jun 29, 2022
4b6661b
:ballot_box_with_check: adding tests
jonasvdd Jun 29, 2022
3d8e3b5
:mag: digging further into the code
jonasvdd Jun 29, 2022
82ed5e9
:broom: extend tests
jvdd Jun 29, 2022
49317d3
Merge pull request #90 from predict-idlab/numeric_object_series
jonasvdd Jun 29, 2022
2ed7423
:dash: new version
jonasvdd Jun 29, 2022
862bc0a
:fire: add support for figure dict input + propagate _grid_str
jvdd Jun 30, 2022
f96a2df
Merge branch 'main' into os_matrix
jvdd Jun 30, 2022
1c2f571
Merge pull request #92 from predict-idlab/figure_dict_input
jvdd Jun 30, 2022
0304972
:package: add serialization (pickle / deepcopy)
jvdd Jul 1, 2022
e0d2fa7
:white_check_mark: add serialization tests
jvdd Jul 1, 2022
2373c63
:pray: update tests
jvdd Jul 1, 2022
f69d356
:pray: fix tests for Mac-OS & Windows
jvdd Jul 1, 2022
71c6e28
:pray:
jvdd Jul 1, 2022
44bd022
Merge pull request #95 from predict-idlab/pray
jvdd Jul 1, 2022
c237069
:white_check_mark: add copy & deepcopy tests
jvdd Jul 3, 2022
c28b7ee
:robot: add python 3.10 to test matrix
jvdd Jul 3, 2022
6741dfd
:pray: convert python versions to string
jvdd Jul 3, 2022
4d1c888
Merge pull request #96 from predict-idlab/add_python3dot10
jvdd Jul 3, 2022
1b6c72b
:art: remove empty space under inline figure_resampler
jvdd Jul 8, 2022
f9929de
:thinking: add inline automatic show dash
jvdd Jul 8, 2022
fd8c2f6
:bike: add show_dash_kwargs to FigureResampler constructor
jvdd Jul 9, 2022
6abf757
:wind_face: formatting + extend tests
jvdd Jul 9, 2022
b80675a
:bread: extend tests
jvdd Jul 11, 2022
6886a61
Merge pull request #97 from predict-idlab/figresampler_display_improv…
jonasvdd Jul 11, 2022
c884c48
:tomato: pass pr_props as property of BaseFigure to super
jvdd Jul 11, 2022
d6ac0f6
Merge branch 'figresampler_display_improvements' into os_matrix
jvdd Jul 11, 2022
74f1d41
:tea: add _grid_str to grid tests
jvdd Jul 11, 2022
126f84a
:pineapple: improving docs + :mag:
jonasvdd Jul 11, 2022
1e4e069
Merge pull request #87 from predict-idlab/os_matrix
jonasvdd Jul 11, 2022
ba889e3
:bangbang: add own lttbc implementation
jonasvdd Jun 25, 2022
b70dc36
:lock:
jonasvdd Jun 27, 2022
d0ce589
:sparkles: adding lttb return index method
jonasvdd Jun 27, 2022
6bb3828
:lock:
jonasvdd Jul 11, 2022
d65f42b
:sparkles:
jonasvdd Jul 11, 2022
e360e88
:mag: :pray:
jonasvdd Jul 12, 2022
1140166
:pray: lon g-> long long for int64 storage
jonasvdd Jul 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9]
os: ['windows-latest', 'macOS-latest', 'ubuntu-latest']
python-version: ['3.7', '3.8', '3.9', '3.10']

steps:
- uses: actions/checkout@v2
Expand Down
41 changes: 17 additions & 24 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
Copyright (c) Jonas Van Der Donckt, Jeroen Van Der Donckt, Emiel Deprost
2021 Ghent University and IMEC vzw with offices at Technologiepark 122, 9052 Ghent,
Belgium - Contact info: http://predict.idlab.ugent.be
MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software for non-commercial educational and research use, including without
limitation the rights to use, copy, modify, merge, publish, distribute and/or
sublicense copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
Copyright (c) 2022 Jonas Van Der Donckt, Jeroen Van Der Donckt, Emiel Deprost.

1. The above copyright notice and this permission notice shall be included in
all copies of the Software.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

2. Permission is restricted to non-commercial educational and research use:
the use of the Software is allowed for teaching purposes and academic
research. Usage by non-academic parties is allowed in a strict research
environment only. The use of the results of the research for commercial
purposes or inclusion in commercial activities requires the permission of
Ghent University and IMEC vzw.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

3. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
84 changes: 84 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import shutil
import sys

from distutils.command.build_ext import build_ext
from distutils.core import Distribution
from distutils.core import Extension
from distutils.errors import CCompilerError
from distutils.errors import DistutilsExecError
from distutils.errors import DistutilsPlatformError

import numpy as np

# C Extensions
with_extensions = True


def get_script_path():
return os.path.dirname(os.path.realpath(sys.argv[0]))

extensions = []
if with_extensions:
extensions = [
Extension(
name="plotly_resampler.aggregation.algorithms.lttbcv2",
sources=["plotly_resampler/aggregation/algorithms/lttbcv2.c"],
define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
include_dirs=[np.get_include(), get_script_path()],
),
]


class BuildFailed(Exception):

pass


class ExtBuilder(build_ext):
# This class allows C extension building to fail.

built_extensions = []

def run(self):
try:
build_ext.run(self)
except (DistutilsPlatformError, FileNotFoundError) as e:
print(" Unable to build the C extensions.")
raise e

def build_extension(self, ext):
try:
build_ext.build_extension(self, ext)
except (CCompilerError, DistutilsExecError, DistutilsPlatformError, ValueError) as e:
print(' Unable to build the "{}" C extension, '.format(ext.name))
raise e


def build(setup_kwargs):
"""
This function is mandatory in order to build the extensions.
"""
distribution = Distribution({"name": "plotly_resampler", "ext_modules": extensions})
distribution.package_dir = "plotly_resampler"

cmd = ExtBuilder(distribution)
cmd.ensure_finalized()
cmd.run()

# Copy built extensions back to the project
for output in cmd.get_outputs():
relative_extension = os.path.relpath(output, cmd.build_lib)
if not os.path.exists(output):
continue

shutil.copyfile(output, relative_extension)
mode = os.stat(relative_extension).st_mode
mode |= (mode & 0o444) >> 2
os.chmod(relative_extension, mode)

return setup_kwargs


if __name__ == "__main__":
build({})
2 changes: 1 addition & 1 deletion plotly_resampler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

__docformat__ = "numpy"
__author__ = "Jonas Van Der Donckt, Jeroen Van Der Donckt, Emiel Deprost"
__version__ = "0.7.2"
__version__ = "0.7.2.2"

__all__ = [
"__version__",
Expand Down
6 changes: 1 addition & 5 deletions plotly_resampler/aggregation/aggregation_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _insert_gap_none(self, s: pd.Series) -> pd.Series:
df_gap_idx = s.index.values[s_idx_diff > 3 * med_diff]
if len(df_gap_idx):
df_res_gap = pd.Series(
index=df_gap_idx, data=None, name=s.name, copy=False
index=df_gap_idx, data=None, name=s.name, copy=False, dtype=s.dtype
)

if isinstance(df_res_gap.index, pd.DatetimeIndex):
Expand Down Expand Up @@ -152,10 +152,6 @@ def aggregate(self, s: pd.Series, n_out: int) -> pd.Series:

self._supports_dtype(s)

# convert the bool values to uint8 (as we will display them on a y-axis)
if str(s.dtype) == "bool":
s = s.astype("uint8")

if len(s) > n_out:
# More samples that n_out -> perform data aggregation
s = self._aggregate(s, n_out=n_out)
Expand Down
61 changes: 19 additions & 42 deletions plotly_resampler/aggregation/aggregators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

import math

import lttbc
import numpy as np
import pandas as pd

from ..aggregation.aggregation_interface import AbstractSeriesAggregator
from .algorithms import lttbcv2


class LTTB(AbstractSeriesAggregator):
Expand Down Expand Up @@ -73,42 +73,19 @@ def __init__(self, interleave_gaps: bool = True, nan_position="end"):
)

def _aggregate(self, s: pd.Series, n_out: int) -> pd.Series:
# if we have categorical data, LTTB will convert the categorical values into
# their numeric codes, i.e., the index position of the category array
s_v = s.cat.codes.values if str(s.dtype) == "category" else s.values
s_i = s.index.values

if s_i.dtype.type == np.datetime64:
# lttbc does not support this datatype -> convert to int
# (where the time is represented in ns)
# REMARK:
# -> additional logic is needed to mitigate rounding errors
# First, the start offset is subtracted, after which the input series
# is set in the already requested format, i.e. np.float64

# NOTE -> Rounding errors can still persist, but this approach is already
# significantly less prone to it than the previos implementation.
s_i0 = s_i[0].astype(np.int64)
idx, data = lttbc.downsample(
(s_i.astype(np.int64) - s_i0).astype(np.float64), s_v, n_out
)

# add the start-offset and convert back to datetime
idx = pd.to_datetime(
idx.astype(np.int64) + s_i0, unit="ns", utc=True
).tz_convert(s.index.tz)
else:
idx, data = lttbc.downsample(s_i, s_v, n_out)
idx = idx.astype(s_i.dtype)
s_i = s.index.values
s_i = s_i.astype(np.int64) if s_i.dtype.type == np.datetime64 else s_i

if str(s.dtype) == "category":
# reconvert the downsampled numeric codes to the category array
data = np.vectorize(s.dtype.categories.values.item)(data.astype(s_v.dtype))
else:
# default case, use the series it's dtype as return type
data = data.astype(s.dtype)
index = lttbcv2.downsample_return_index(s_i, s_v, n_out)

return pd.Series(index=idx, data=data, name=str(s.name), copy=False)
return pd.Series(
index=s.index[index],
data=s.values[index],
name=str(s.name),
copy=False,
)


class MinMaxOverlapAggregator(AbstractSeriesAggregator):
Expand Down Expand Up @@ -166,14 +143,14 @@ def _aggregate(self, s: pd.Series, n_out: int) -> pd.Series:
# Calculate the argmin & argmax on the reshaped view of `s` &
# add the corresponding offset
argmin = (
s.iloc[: block_size * offset.shape[0]]
.values.reshape(-1, block_size)
s.values[: block_size * offset.shape[0]]
.reshape(-1, block_size)
.argmin(axis=1)
+ offset
)
argmax = (
s.iloc[argmax_offset : block_size * offset.shape[0] + argmax_offset]
.values.reshape(-1, block_size)
s.values[argmax_offset : block_size * offset.shape[0] + argmax_offset]
.reshape(-1, block_size)
.argmax(axis=1)
+ offset
+ argmax_offset
Expand Down Expand Up @@ -231,14 +208,14 @@ def _aggregate(self, s: pd.Series, n_out: int) -> pd.Series:
# Calculate the argmin & argmax on the reshaped view of `s` &
# add the corresponding offset
argmin = (
s.iloc[: block_size * offset.shape[0]]
.values.reshape(-1, block_size)
s.values[: block_size * offset.shape[0]]
.reshape(-1, block_size)
.argmin(axis=1)
+ offset
)
argmax = (
s.iloc[: block_size * offset.shape[0]]
.values.reshape(-1, block_size)
s.values[: block_size * offset.shape[0]]
.reshape(-1, block_size)
.argmax(axis=1)
+ offset
)
Expand Down Expand Up @@ -297,7 +274,7 @@ def __init__(self, interleave_gaps: bool = True, nan_position="end"):
)

def _aggregate(self, s: pd.Series, n_out: int) -> pd.Series:
if s.shape[0] > n_out * 1_000:
if s.shape[0] > n_out * 2_000:
s = self.minmax._aggregate(s, n_out * 50)
return self.lttb._aggregate(s, n_out)

Expand Down
Loading