From a68ec2dd646a16153628294bb2a04eaff859236f Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 17:06:11 +1000 Subject: [PATCH 1/8] use new DataFrame output with short names + hctsa names returned --- pyproject.toml | 4 +++ setup.py | 3 +- src/pycatch22/catch22.py | 27 +++++++++-------- tests/test_features.py | 4 +-- tests/unit_tests.py | 63 +++++++++++++++++----------------------- 5 files changed, 48 insertions(+), 53 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de9cddb..07b5876 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,10 @@ classifiers = [ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", ] +dependencies = [ + "pandas", + "numpy" +] [project.urls] "GitHub Repository (pycatch22)" = "https://github.com/DynamicsAndNeuralSystems/pycatch22" diff --git a/setup.py b/setup.py index fc2a169..387e4ce 100644 --- a/setup.py +++ b/setup.py @@ -26,5 +26,6 @@ packages = find_packages(where = "src", include = ["pycatch22"]), package_dir = {"": "src"}, - ext_modules = [extension_mod] + ext_modules = [extension_mod], + install_requires = ["numpy", "pandas"] ) diff --git a/src/pycatch22/catch22.py b/src/pycatch22/catch22.py index 5eaa932..e9338d8 100644 --- a/src/pycatch22/catch22.py +++ b/src/pycatch22/catch22.py @@ -1,6 +1,7 @@ import catch22_C +import pandas as pd -def catch22_all(data, catch24=False, short_names=False): +def catch22_all(data, catch24=False): ''' Extract the catch22 feature set from an input time series. @@ -10,12 +11,10 @@ def catch22_all(data, catch24=False, short_names=False): Input time-series data. catch24 : bool, optional If True, include the two catch24 features (mean and standard deviation) in the output. - short_names : bool, optional - If True, also include the short names of the features in the output. ''' - features = [ + features_hctsa = [ 'DN_HistogramMode_5', 'DN_HistogramMode_10', 'CO_f1ecac', @@ -40,7 +39,7 @@ def catch22_all(data, catch24=False, short_names=False): 'FC_LocalSimple_mean3_stderr' ] - features_short = [ + features = [ 'mode_5', 'mode_10', 'acf_timescale', @@ -66,18 +65,18 @@ def catch22_all(data, catch24=False, short_names=False): ] if catch24: - features.append('DN_Mean') - features.append('DN_Spread_Std') - features_short.append('mean') - features_short.append('SD') + features_hctsa.append('DN_Mean') + features_hctsa.append('DN_Spread_Std') + features.append('mean') + features.append('SD') data = list(data) featureOut = [] - for f in features: + for f in features_hctsa: featureFun = getattr(catch22_C, f) featureOut.append(featureFun(data)) - if short_names: - return {'names': features, 'short_names': features_short, 'values': featureOut} - else: - return {'names': features, 'values': featureOut} + # convert to a dataframe + feature_results = pd.DataFrame({'feature': features, 'value': featureOut, 'hctsa_name': features_hctsa}) + + return feature_results diff --git a/tests/test_features.py b/tests/test_features.py index 3b20542..5dc50b3 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -50,7 +50,7 @@ def load_expected_outputs(): def compute_new_features(): """Computes new feature outputs on same benchmarking dataset and then returns dictionary of datasets in the same format as - the loaded expected outputs dictionary""" + the loaded expected outputs dictionary for direct comparison.""" benchmark_datasets = load_benchmark_datasets() datasets = benchmark_datasets.keys() @@ -60,7 +60,7 @@ def compute_new_features(): print(f"Computing features for: {dset}...") test_data = benchmark_datasets[dset] res = catch22.catch22_all(test_data, catch24=True) - for (name, val) in zip(res['names'], res['values']): + for (name, val) in zip(res['hctsa_name'], res['value']): dataset_dict_single[name] = float(val) dataset_dicts[dset] = dataset_dict_single diff --git a/tests/unit_tests.py b/tests/unit_tests.py index 48e2098..5082fa8 100644 --- a/tests/unit_tests.py +++ b/tests/unit_tests.py @@ -1,60 +1,51 @@ import pycatch22 as catch22 import pytest import numpy as np +import pandas as pd # unit tests -def expected_output(res, catch24=False, short_names=False): +def expected_output(res, catch24=False): which_set = "Catch24" if catch24 else "Catch22" num_features = 24 if catch24 else 22 # check if the output is a dictionary - assert isinstance(res, dict), f"{which_set} did not return a dictionary. Unexpected output." - # check if the dictionary has two keys - names and values - if short_names: - dict_length = len(res) - assert dict_length == 3, f"{which_set} returned a dictionary of length {dict_length}. Expected length 3 for short_names = true" - assert all(key in ['names', 'short_names', 'values'] for key in res.keys()), f"{which_set} returned unexpected keys for short_names = True" - - # check the short names list - assert all(isinstance(name, str) for name in res['short_names']), f"{which_set} expected all returned short names to be strings." - length_of_names = len(res['short_names']) - assert length_of_names == num_features, f"Expected {num_features} short names for {which_set}, got {length_of_names} instead." - - else: - assert len(res) == 2, f"{which_set} returned an unexpected dictionary size." - # check if the keys are 'names' and 'values' - assert all(key in ['names', 'values'] for key in res.keys()), f"{which_set} returned unexpected keys." - - # check the 'names' list - assert isinstance(res['names'], list), f"{which_set} expected list of names (str), got unexpected output." - length_of_names = len(res['names']) + assert isinstance(res, pd.DataFrame), f"{which_set} did not return a DataFrame. Unexpected output." + # check if the dataframe has three columns - features, values and hctsa_names + assert len(res.columns) == 3, f"{which_set} returned a DataFrame with an unexpected number of columns." + assert all(col_name in ['feature', 'value', 'hctsa_name'] for col_name in res.columns), f"{which_set} returned unexpected columns." + # check the column datatypes + assert all(isinstance(fname, str) for fname in res.feature), f"{which_set} expected all returned feature names to be strings." + length_of_names = len(res.feature) assert length_of_names == num_features, f"Expected {num_features} names for {which_set}, got {length_of_names} instead." - assert all(isinstance(name, str) for name in res['names']), f"{which_set} expected all returned names to be strings." - # check the 'values' list - assert isinstance(res['values'], list), f"{which_set} expected list of values, got unexpected output." - length_of_vals = len(res['values']) + # check the 'value' column + length_of_vals = len(res.value) assert length_of_vals == num_features, f"Expected {num_features} values for {which_set}, got {length_of_vals} instead." - assert all(isinstance(val, (float, int)) for val in res['values']), f"{which_set} expected all returned feature values to be floats or integers." + assert all(isinstance(val, (float, int)) for val in res.value), f"{which_set} expected all returned feature values to be floats or integers." + + # check the `hctsa_name` column + length_of_hctsa_names = len(res.hctsa_name) + assert length_of_hctsa_names == num_features, f"Expected {num_features} hctsa names for {which_set}, got {length_of_hctsa_names} instead." + assert all(isinstance(fname, str) for fname in res.hctsa_name), f"{which_set} expected all returned hctsa feature names to be strings." def test_catch22_runs(): # test whether catch22 works on some random data tsData = np.random.randn(100) - res = catch22.catch22_all(tsData, catch24=False, short_names=False) - expected_output(res, catch24=False, short_names=False) + res = catch22.catch22_all(tsData, catch24=False) + expected_output(res, catch24=False) def test_catch24_runs(): # test whether catch24 works on some random data tsData = np.random.randn(100) - res = catch22.catch22_all(tsData, catch24=True, short_names=False) - expected_output(res, catch24=True, short_names=False) + res = catch22.catch22_all(tsData, catch24=True) + expected_output(res, catch24=True) def test_short_names_returned(): # test whether catch22/catch24 returns short names tsData = np.random.randn(100) - res = catch22.catch22_all(tsData, catch24=False, short_names=True) - expected_output(res, catch24=False, short_names=True) - res2 = catch22.catch22_all(tsData, catch24=True, short_names=True) - expected_output(res2, catch24=True, short_names=True) + res = catch22.catch22_all(tsData, catch24=False) + expected_output(res, catch24=False) + res2 = catch22.catch22_all(tsData, catch24=True) + expected_output(res2, catch24=True) def test_valid_input_types(): # should accept tuples, arrays and lists @@ -76,8 +67,8 @@ def test_inf_and_nan_input(): base_data = np.random.randn(100) base_data[0] = val_type res = catch22.catch22_all(base_data, catch24=False) - expected_output(res, catch24=False, short_names=False) - res_values = res['values'] + expected_output(res, catch24=False) + res_values = res['value'] for i, val in enumerate(res_values): if i in zero_outputs: # check that value is 0 or 0.0 From 5a7553c17bce480d7363607ed7d279f03c07a681 Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 17:17:44 +1000 Subject: [PATCH 2/8] bump to v0.5.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 07b5876..964ad09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pycatch22" -version = "0.4.5" +version = "0.5.0" authors = [ {name = "Carl H Lubba"}, {email = "carl.lubba@gmx.de"}, From 412d57c5653f16715afe32cdb10a55e0540cf8d7 Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 17:19:11 +1000 Subject: [PATCH 3/8] add python 3.12 runners for unit testing --- .github/workflows/run_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_unit_tests.yaml b/.github/workflows/run_unit_tests.yaml index 637ab41..80a2086 100644 --- a/.github/workflows/run_unit_tests.yaml +++ b/.github/workflows/run_unit_tests.yaml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - name: Setup python ${{ matrix.python-version }} From b11836dc48cf29ab77ba1585d9341d440a0befe2 Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 17:34:51 +1000 Subject: [PATCH 4/8] update README with badges and add code of conduct + security page --- .github/CODE_OF_CONDUCT.md | 127 +++++++++++++++++++++++++++++++++++++ .github/SECURITY.md | 16 +++++ README.md | 2 + 3 files changed, 145 insertions(+) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/SECURITY.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..409f6ce --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge 🤝 + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards 🌟 + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities 🛡️ + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope 🌐 + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement 👮🏻 + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement [here](joshua.moore@sydney.edu.au). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines 📋 + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction ✔️ + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning ❗ + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban ⏳ + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban ❌ + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution 👍 + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..81bb7bf --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,16 @@ +# Reporting Security Issues 🛡️ + +The _catch22_ team and community take security bugs in _catch22_ seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, please use the GitHub Security Advisory tab. + +The _catch22_ team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. + +## Supported Versions + +The following versions of _catch22_ are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 0.5.0 | :white_check_mark: | diff --git a/README.md b/README.md index 077131d..f4df02f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@

+
+ Python 3.8 | 3.9 | 3.10 | 3.11 | 3.12

From df63fe3894dd778cf703fb1afe0e64d305c29b98 Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 17:44:09 +1000 Subject: [PATCH 5/8] add darkmode logo --- .github/SECURITY.md | 1 + README.md | 7 ++++++- img/catch22_logo_square_darkmode.png | Bin 0 -> 24289 bytes 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 img/catch22_logo_square_darkmode.png diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 81bb7bf..e640314 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -14,3 +14,4 @@ currently being supported with security updates. | Version | Supported | | ------- | ------------------ | | 0.5.0 | :white_check_mark: | +| 0.4.5 | :white_check_mark: | diff --git a/README.md b/README.md index f4df02f..a0107fd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -

catch22 logo

+

+ + + catch22 logo + +

pycatch22: CAnonical Time-series CHaracteristics in python

diff --git a/img/catch22_logo_square_darkmode.png b/img/catch22_logo_square_darkmode.png new file mode 100644 index 0000000000000000000000000000000000000000..184ca0ad4990a918ad7b3def6f8bb86f7dd23002 GIT binary patch literal 24289 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

W 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 Date: Tue, 4 Jun 2024 18:00:20 +1000 Subject: [PATCH 6/8] remove short names optional from README --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a0107fd..b1185e9 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,10 @@ tsData = [1,2,4,3] # (or more interesting data!) pycatch22.CO_f1ecac(tsData) ``` -All features are bundled in the method `catch22_all`, which also accepts `numpy` arrays and gives back a dictionary containing the entries `catch22_all['names']` for feature names and `catch22_all['values']` for feature outputs. +All features are bundled in the method `catch22_all`, which also accepts `numpy` arrays and gives back a DataFrame containing the columns: +- `feature` for (short) feature names (as outlined in the GitBook [Feature overview table](https://time-series-features.gitbook.io/catch22/feature-descriptions/feature-overview-table)). +- `hctsa_name` for (long) feature names as they appear in HCTSA. +- `value` for feature outputs. Usage (computing 22 features: _catch22_): @@ -84,13 +87,6 @@ Usage (computing 24 features: _catch24_ = _catch22_ + mean + standard deviation) pycatch22.catch22_all(tsData,catch24=True) ``` -We also include a 'short name' for each feature for easier reference (as outlined in the GitBook [Feature overview table](https://time-series-features.gitbook.io/catch22/feature-descriptions/feature-overview-table)). -These short names can be included in the output from `catch22_all()` by setting `short_names=True` as follows: - -```python3 -pycatch22.catch22_all(tsData,catch24=True,short_names=True) -``` - ### Template analysis script Thanks to [@jmoo2880](https://github.com/jmoo2880) for putting together a [demonstration notebook](https://github.com/jmoo2880/c22-usage-examples/) for using pycatch22 to extract features from a time-series dataset. From 3f59c2e06300b76fc724ed30c44623c94501197f Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 18:04:50 +1000 Subject: [PATCH 7/8] remove short names optional from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1185e9..d913ebb 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ tsData = [1,2,4,3] # (or more interesting data!) pycatch22.CO_f1ecac(tsData) ``` -All features are bundled in the method `catch22_all`, which also accepts `numpy` arrays and gives back a DataFrame containing the columns: +All features are bundled in the method `catch22_all`, which also accepts `numpy` arrays and gives back a `DataFrame` containing the columns: - `feature` for (short) feature names (as outlined in the GitBook [Feature overview table](https://time-series-features.gitbook.io/catch22/feature-descriptions/feature-overview-table)). - `hctsa_name` for (long) feature names as they appear in HCTSA. - `value` for feature outputs. From cf20d2409fd5dc0668f83f8cc6848cc8dc2eb85e Mon Sep 17 00:00:00 2001 From: jmoo2880 Date: Tue, 4 Jun 2024 19:40:14 +1000 Subject: [PATCH 8/8] bump version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 964ad09..9d24a38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pycatch22" -version = "0.5.0" +version = "1.0.0" authors = [ {name = "Carl H Lubba"}, {email = "carl.lubba@gmx.de"},