├── .gitignore ├── CITATION.cff ├── LICENSE ├── README.md ├── environment.yml ├── examples ├── results │ ├── -0.00-BirdChicken.pdf │ ├── -0.00-DistalPhalanxOutlineCorrect.pdf │ ├── -0.00-GunPoint.pdf │ ├── -0.00-GunPointAgeSpan.pdf │ ├── -0.00-ItalyPowerDemand.pdf │ ├── -0.00-Lightning2.pdf │ ├── -0.00-PhalangesOutlinesCorrect.pdf │ ├── -0.01-MiddlePhalanxOutlineCorrect.pdf │ ├── -0.03-Strawberry.pdf │ ├── 0.00-LargeKitchenAppliances.pdf │ ├── 0.01-DodgerLoopGame.pdf │ ├── 0.01-HouseTwenty.pdf │ ├── 0.01-ShapeletSim.pdf │ ├── 0.02-Herring.pdf │ ├── 0.04-GunPointOldVersusYoung.pdf │ ├── 0.04-Wine.pdf │ ├── 0.05-Ham.pdf │ ├── 0.05-ProximalPhalanxOutlineCorrect.pdf │ ├── 0.07-Computers.pdf │ ├── 0.07-PowerCons.pdf │ ├── 0.08-BME.pdf │ ├── 0.08-Haptics.pdf │ ├── 0.08-MedicalImages.pdf │ ├── 0.08-SmoothSubspace.pdf │ ├── 0.08-SonyAIBORobotSurface1.pdf │ ├── 0.08-WormsTwoClass.pdf │ ├── 0.09-Beef.pdf │ ├── 0.10-AllGestureWiimoteZ.pdf │ ├── 0.10-GestureMidAirD3.pdf │ ├── 0.10-WordSynonyms.pdf │ ├── 0.11-AllGestureWiimoteX.pdf │ ├── 0.11-AllGestureWiimoteY.pdf │ ├── 0.12-Adiac.pdf │ ├── 0.13-CricketY.pdf │ ├── 0.13-CricketZ.pdf │ ├── 0.13-DodgerLoopDay.pdf │ ├── 0.13-Lightning7.pdf │ ├── 0.14-FiftyWords.pdf │ ├── 0.14-Worms.pdf │ ├── 0.16-CricketX.pdf │ ├── 0.16-ECGFiveDays.pdf │ ├── 0.17-Car.pdf │ ├── 0.19-Rock.pdf │ ├── 0.20-DistalPhalanxOutlineAgeGroup.pdf │ ├── 0.20-OSULeaf.pdf │ ├── 0.21-UMD.pdf │ ├── 0.22-SonyAIBORobotSurface2.pdf │ ├── 0.23-PickupGestureWiimoteZ.pdf │ ├── 0.24-GestureMidAirD1.pdf │ ├── 0.25-ECG200.pdf │ ├── 0.26-DistalPhalanxTW.pdf │ ├── 0.26-ShakeGestureWiimoteZ.pdf │ ├── 0.27-GestureMidAirD2.pdf │ ├── 0.27-MelbournePedestrian.pdf │ ├── 0.34-ArrowHead.pdf │ ├── 0.34-BeetleFly.pdf │ ├── 0.35-ShapesAll.pdf │ ├── 0.36-GesturePebbleZ1.pdf │ ├── 0.36-ToeSegmentation1.pdf │ ├── 0.37-SyntheticControl.pdf │ ├── 0.37-ToeSegmentation2.pdf │ ├── 0.39-GesturePebbleZ2.pdf │ ├── 0.39-GunPointMaleVersusFemale.pdf │ ├── 0.40-SwedishLeaf.pdf │ ├── 0.42-MiddlePhalanxOutlineAgeGroup.pdf │ ├── 0.42-ProximalPhalanxTW.pdf │ ├── 0.43-InsectEPGRegularTrain.pdf │ ├── 0.44-InsectEPGSmallTrain.pdf │ ├── 0.44-MoteStrain.pdf │ ├── 0.51-ProximalPhalanxOutlineAgeGroup.pdf │ ├── 0.55-Fish.pdf │ ├── 0.56-FaceFour.pdf │ ├── 0.56-FreezerRegularTrain.pdf │ ├── 0.58-Meat.pdf │ ├── 0.58-MiddlePhalanxTW.pdf │ ├── 0.75-Symbols.pdf │ ├── 0.76-DodgerLoopWeekend.pdf │ ├── 0.76-OliveOil.pdf │ ├── 0.77-TwoLeadECG.pdf │ ├── 0.78-DiatomSizeReduction.pdf │ ├── 0.79-Coffee.pdf │ ├── 0.81-CBF.pdf │ ├── 0.82-Chinatown.pdf │ ├── 0.90-Fungi.pdf │ ├── 0.99-Plane.pdf │ ├── 0.99-Trace.pdf │ └── README.md └── scripts │ └── Trace_example.py ├── kgraph ├── __init__.py ├── kgraph_core.py └── utils.py ├── requirements.txt ├── ressources ├── Trace_cluster_interpretation.jpg ├── Trace_kgraph.jpg ├── kGraph_logo.png └── pipeline.png ├── setup.cfg ├── setup.py └── utils └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | .DS_Store 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.0.1 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: Boniol 5 | given-names: Paul 6 | - family-names: Tiano 7 | given-names: Donato 8 | - family-names: Bonifati 9 | given-names: Angela 10 | - family-names: Palpanas 11 | given-names: Themis 12 | title: "$k$-Graph: A Graph Embedding for Interpretable Time Series Clustering" 13 | date-released: 2025 14 | url: "https://github.com/boniolp/kGraph" 15 | preferred-citation: 16 | type: article 17 | authors: 18 | - family-names: Boniol 19 | given-names: Paul 20 | - family-names: Tiano 21 | given-names: Donato 22 | - family-names: Bonifati 23 | given-names: Angela 24 | - family-names: Palpanas 25 | given-names: Themis 26 | doi: 10.1109/TKDE.2025.3543946 27 | journal: "IEEE Transactions on Knowledge and Data Engineering" 28 | title: "$k$-Graph: A Graph Embedding for Interpretable Time Series Clustering" 29 | issue: 5 30 | volume: 37 31 | year: 2025 32 | start: 2680 33 | end: 2694 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Paul Boniol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 |

$k$-Graph

7 |

A Graph Embedding for Interpretable Time Series Clustering

8 | 9 |
10 |

11 | PyPI - Downloads PyPI GitHub GitHub issues PyPI - Python Version 12 |

13 |
14 | 15 | 16 | ## Table of Contents 17 | - [Introduction](#k-graph-in-short) 18 | - [Installation](#getting-started) 19 | - [Usage](#usage) 20 | - [Visualization](#visualization-tools) 21 | - [Citation](#references) 22 | - [Contributors](#contributors) 23 | 24 | ## $k$-Graph in short 25 | 26 | $k$-Graph is an explainable and interpretable Graph-based time series clustering. $k$-Graph is divided into three steps: (i) Graph embedding, (ii) Graph clustering, and (iii) Consensus Clustering. In practice, it first projects the time series into a graph and repeats the operation for multiple pattern lengths. For each pattern length, we use the corresponding graph to cluster the time series (based on the frequency of the nodes and edges for each time series). We then find a consensus between all pattern lengths and use the consensus as clustering labels. Thanks to the graph representation of the time series (into a unique graph), $k$-Graph can be utilized for variable-length time series. Moreover, we provide a way to select the most interpretable graph for the resulting clustering partition and allow users to visualize the subsequences contained in the most representative and exclusive nodes. 27 | 28 |

29 | 30 |

31 | 32 | ### 🔍 Features 33 | - 📊 Clusters time series using graph embeddings 34 | - 🔄 Supports variable-length time series analysis 35 | - 🧠 Provides interpretable graph visualizations 36 | 37 | ### 🌐 Try it Online 38 | 39 | Explore $k$-Graph with our interactive tool: 👉 [**GrapHint Visualization Tool**](https://graphint.streamlit.app/) 40 | 41 | ### 📁 Project Structure 42 | 43 | ```(bash) 44 | kGraph/ 45 | ├── kgraph/ # Core implementation 46 | ├── examples/ # Example usage scripts 47 | ├── ressources/ # Visuals and images 48 | ├── utils/ # utils methods for loading datasets 49 | ├── requirements.txt # Dependencies 50 | └── README.md 51 | ``` 52 | 53 | ## Getting started 54 | 55 | The easiest solution to install $k$-Graph is to run the following command: 56 | 57 | ```(bash) 58 | pip install kgraph-ts 59 | ``` 60 | 61 | Graphviz and pyGraphviz can be used to obtain better visualisation for $k$-Graph. These two packages are not necessary to run $k$-graph. If not installed, a random layout is used to plot the graphs. 62 | To benefit from a better visualisation of the graphs, please install Graphviz and pyGraphviz as follows: 63 | 64 | #### For Mac: 65 | 66 | ```(bash) 67 | brew install graphviz 68 | ``` 69 | 70 | #### For Linux (Ubuntu): 71 | 72 | ```(bash) 73 | sudo apt install graphviz 74 | ``` 75 | 76 | #### For Windows: 77 | 78 | Stable Windows install packages are listed [here](https://graphviz.org/download/) 79 | 80 | Once Graphviz is installed, you can install pygraphviz as follows: 81 | 82 | ```(bash) 83 | pip install pygraphviz 84 | ``` 85 | 86 | 87 | 88 | ### Manual installation 89 | 90 | You can also install manually $k$-Graph by following the instructions below. 91 | All Python packages needed are listed in [requirements.txt](https://github.com/boniolp/kGraph/blob/main/requirements.txt) file and can be installed simply using the pip command: 92 | 93 | ```(bash) 94 | conda env create --file environment.yml 95 | conda activate kgraph 96 | pip install -r requirements.txt 97 | ``` 98 | You can then install $k$-Graph locally with the following command: 99 | 100 | ```(bash) 101 | pip install . 102 | ``` 103 | 104 | 105 | ## Usage 106 | 107 | In order to play with $k$-Graph, please check the [UCR archive](https://www.cs.ucr.edu/%7Eeamonn/time_series_data_2018/). We depict below a code snippet demonstrating how to use $k$-Graph. 108 | 109 | ```python 110 | import sys 111 | import pandas as pd 112 | import numpy as np 113 | import networkx as nx 114 | import matplotlib.pyplot as plt 115 | from sklearn.metrics import adjusted_rand_score 116 | 117 | sys.path.insert(1, './utils/') 118 | from utils import fetch_ucr_dataset 119 | 120 | from kgraph import kGraph 121 | 122 | 123 | path = "/Path/to/UCRArchive_2018/" 124 | data = fetch_ucr_dataset('Trace',path) 125 | X = np.concatenate([data['data_train'],data['data_test']],axis=0) 126 | y = np.concatenate([data['target_train'],data['target_test']],axis=0) 127 | 128 | 129 | # Executing kGraph 130 | clf = kGraph(n_clusters=len(set(y)),n_lengths=10,n_jobs=4) 131 | clf.fit(X) 132 | 133 | print("ARI score: ",adjusted_rand_score(clf.labels_,y)) 134 | ``` 135 | ``` 136 | Running kGraph for the following length: [36, 72, 10, 45, 81, 18, 54, 90, 27, 63] 137 | Graphs computation done! (36.71151804924011 s) 138 | Consensus done! (0.03878021240234375 s) 139 | Ensemble clustering done! (0.0060100555419921875 s) 140 | ARI score: 0.986598879940902 141 | ``` 142 | 143 | For variable-length time series datasets, $k$-Graph has to be initialized as follows: 144 | 145 | ```python 146 | clf = kGraph(n_clusters=len(set(y)),variable_length=True,n_lengths=10,n_jobs=4) 147 | ``` 148 | 149 | ### Visualization tools 150 | 151 | We provide visualization methods to plot the graph and the identified clusters (i.e., graphoids). After running $k$-Graph, you can run the following code to plot the graphs partitioned in different clusters (grey are nodes that are not associated with a specific cluster). 152 | 153 | ```python 154 | clf.show_graphoids(group=True,save_fig=True,namefile='Trace_kgraph') 155 | ``` 156 |

157 | 158 |

159 | 160 | Instead of visualizing the graph, we can directly retrieve the most representative nodes for each cluster with the following code: 161 | 162 | ```python 163 | nb_patterns = 1 164 | 165 | #Get the most representative nodes 166 | nodes = clf.interprete(nb_patterns=nb_patterns) 167 | 168 | plt.figure(figsize=(10,4*nb_patterns)) 169 | count = 0 170 | for j in range(nb_patterns): 171 | for i,node in enumerate(nodes.keys()): 172 | 173 | # Get the time series for the corresponding node 174 | mean,sup,inf = clf.get_node_ts(X=X,node=nodes[node][j][0]) 175 | 176 | count += 1 177 | plt.subplot(nb_patterns,len(nodes.keys()),count) 178 | plt.fill_between(x=list(range(int(clf.optimal_length))),y1=inf,y2=sup,alpha=0.2) 179 | plt.plot(mean,color='black') 180 | plt.plot(inf,color='black',alpha=0.6,linestyle='--') 181 | plt.plot(sup,color='black',alpha=0.6,linestyle='--') 182 | plt.title('node {} for cluster {}: \n (representativity: {:.3f} \n exclusivity : {:.3f})'.format(nodes[node][j][0],node,nodes[node][j][3],nodes[node][j][2])) 183 | plt.tight_layout() 184 | 185 | plt.savefig('Trace_cluster_interpretation.jpg') 186 | plt.close() 187 | ``` 188 |

189 | 190 |

191 | 192 | You can find a script containing all the code above [here](https://github.com/boniolp/kGraph/blob/main/examples/scripts/Trace_example.py). 193 | 194 | ## References 195 | 196 | $k$-Graph has been accepted for publication IEEE Transactions on Knowledge and Data Engineering (TKDE). You may find the preprint version [here](https://arxiv.org/abs/2502.13049). 197 | If you use $k$-Graph in your project or research, cite the following paper: 198 | 199 | > P. Boniol, D. Tiano, A. Bonifati and T. Palpanas, " k -Graph: A Graph Embedding for Interpretable Time Series Clustering," in IEEE Transactions on Knowledge and Data Engineering, doi: 10.1109/TKDE.2025.3543946. 200 | 201 | ```bibtex 202 | @ARTICLE{10896823, 203 | author={Boniol, Paul and Tiano, Donato and Bonifati, Angela and Palpanas, Themis}, 204 | journal={IEEE Transactions on Knowledge and Data Engineering}, 205 | title={$k$-Graph: A Graph Embedding for Interpretable Time Series Clustering}, 206 | year={2025}, 207 | volume={37}, 208 | number={5}, 209 | pages={2680-2694}, 210 | keywords={Time series analysis;Feature extraction;Clustering algorithms;Accuracy;Heuristic algorithms;Clustering methods;Training;Shape;Partitioning algorithms;Directed graphs;Time Series;Clustering;Interpretability}, 211 | doi={10.1109/TKDE.2025.3543946}} 212 | ``` 213 | 214 | 215 | ## Contributors 216 | 217 | - [Paul Boniol](https://boniolp.github.io/), Inria, ENS, PSL University, CNRS 218 | - [Donato Tiano](https://liris.cnrs.fr/en/member-page/donato-tiano), Università degli Studi di Modena e Reggio Emilia 219 | - [Angela Bonifati](https://perso.liris.cnrs.fr/angela.bonifati/), Lyon 1 University, IUF, Liris CNRS 220 | - [Themis Palpanas](https://helios2.mi.parisdescartes.fr/~themisp/), Université Paris Cité, IUF 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: kgraph 2 | channels: 3 | - defaults 4 | dependencies: 5 | - ca-certificates=2023.08.22=hca03da5_0 6 | - libcxx=14.0.6=h848a8c0_0 7 | - libffi=3.4.4=hca03da5_0 8 | - ncurses=6.4=h313beb8_0 9 | - openssl=3.0.12=h1a28f6b_0 10 | - pip=23.3.1=py39hca03da5_0 11 | - python=3.9.18=hb885b13_0 12 | - readline=8.2=h1a28f6b_0 13 | - setuptools=68.0.0=py39hca03da5_0 14 | - sqlite=3.41.2=h80987f9_0 15 | - tk=8.6.12=hb8d0fd4_0 16 | - wheel=0.41.2=py39hca03da5_0 17 | - xz=5.4.2=h80987f9_0 18 | - zlib=1.2.13=h5a0b063_0 19 | -------------------------------------------------------------------------------- /examples/results/-0.00-BirdChicken.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-BirdChicken.pdf -------------------------------------------------------------------------------- /examples/results/-0.00-DistalPhalanxOutlineCorrect.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-DistalPhalanxOutlineCorrect.pdf -------------------------------------------------------------------------------- /examples/results/-0.00-GunPoint.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-GunPoint.pdf -------------------------------------------------------------------------------- /examples/results/-0.00-GunPointAgeSpan.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-GunPointAgeSpan.pdf -------------------------------------------------------------------------------- /examples/results/-0.00-ItalyPowerDemand.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-ItalyPowerDemand.pdf -------------------------------------------------------------------------------- /examples/results/-0.00-Lightning2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-Lightning2.pdf -------------------------------------------------------------------------------- /examples/results/-0.00-PhalangesOutlinesCorrect.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.00-PhalangesOutlinesCorrect.pdf -------------------------------------------------------------------------------- /examples/results/-0.01-MiddlePhalanxOutlineCorrect.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.01-MiddlePhalanxOutlineCorrect.pdf -------------------------------------------------------------------------------- /examples/results/-0.03-Strawberry.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/-0.03-Strawberry.pdf -------------------------------------------------------------------------------- /examples/results/0.00-LargeKitchenAppliances.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.00-LargeKitchenAppliances.pdf -------------------------------------------------------------------------------- /examples/results/0.01-DodgerLoopGame.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.01-DodgerLoopGame.pdf -------------------------------------------------------------------------------- /examples/results/0.01-HouseTwenty.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.01-HouseTwenty.pdf -------------------------------------------------------------------------------- /examples/results/0.01-ShapeletSim.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.01-ShapeletSim.pdf -------------------------------------------------------------------------------- /examples/results/0.02-Herring.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.02-Herring.pdf -------------------------------------------------------------------------------- /examples/results/0.04-GunPointOldVersusYoung.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.04-GunPointOldVersusYoung.pdf -------------------------------------------------------------------------------- /examples/results/0.04-Wine.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.04-Wine.pdf -------------------------------------------------------------------------------- /examples/results/0.05-Ham.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.05-Ham.pdf -------------------------------------------------------------------------------- /examples/results/0.05-ProximalPhalanxOutlineCorrect.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.05-ProximalPhalanxOutlineCorrect.pdf -------------------------------------------------------------------------------- /examples/results/0.07-Computers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.07-Computers.pdf -------------------------------------------------------------------------------- /examples/results/0.07-PowerCons.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.07-PowerCons.pdf -------------------------------------------------------------------------------- /examples/results/0.08-BME.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.08-BME.pdf -------------------------------------------------------------------------------- /examples/results/0.08-Haptics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.08-Haptics.pdf -------------------------------------------------------------------------------- /examples/results/0.08-MedicalImages.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.08-MedicalImages.pdf -------------------------------------------------------------------------------- /examples/results/0.08-SmoothSubspace.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.08-SmoothSubspace.pdf -------------------------------------------------------------------------------- /examples/results/0.08-SonyAIBORobotSurface1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.08-SonyAIBORobotSurface1.pdf -------------------------------------------------------------------------------- /examples/results/0.08-WormsTwoClass.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.08-WormsTwoClass.pdf -------------------------------------------------------------------------------- /examples/results/0.09-Beef.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.09-Beef.pdf -------------------------------------------------------------------------------- /examples/results/0.10-AllGestureWiimoteZ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.10-AllGestureWiimoteZ.pdf -------------------------------------------------------------------------------- /examples/results/0.10-GestureMidAirD3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.10-GestureMidAirD3.pdf -------------------------------------------------------------------------------- /examples/results/0.10-WordSynonyms.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.10-WordSynonyms.pdf -------------------------------------------------------------------------------- /examples/results/0.11-AllGestureWiimoteX.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.11-AllGestureWiimoteX.pdf -------------------------------------------------------------------------------- /examples/results/0.11-AllGestureWiimoteY.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.11-AllGestureWiimoteY.pdf -------------------------------------------------------------------------------- /examples/results/0.12-Adiac.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.12-Adiac.pdf -------------------------------------------------------------------------------- /examples/results/0.13-CricketY.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.13-CricketY.pdf -------------------------------------------------------------------------------- /examples/results/0.13-CricketZ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.13-CricketZ.pdf -------------------------------------------------------------------------------- /examples/results/0.13-DodgerLoopDay.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.13-DodgerLoopDay.pdf -------------------------------------------------------------------------------- /examples/results/0.13-Lightning7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.13-Lightning7.pdf -------------------------------------------------------------------------------- /examples/results/0.14-FiftyWords.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.14-FiftyWords.pdf -------------------------------------------------------------------------------- /examples/results/0.14-Worms.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.14-Worms.pdf -------------------------------------------------------------------------------- /examples/results/0.16-CricketX.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.16-CricketX.pdf -------------------------------------------------------------------------------- /examples/results/0.16-ECGFiveDays.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.16-ECGFiveDays.pdf -------------------------------------------------------------------------------- /examples/results/0.17-Car.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.17-Car.pdf -------------------------------------------------------------------------------- /examples/results/0.19-Rock.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.19-Rock.pdf -------------------------------------------------------------------------------- /examples/results/0.20-DistalPhalanxOutlineAgeGroup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.20-DistalPhalanxOutlineAgeGroup.pdf -------------------------------------------------------------------------------- /examples/results/0.20-OSULeaf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.20-OSULeaf.pdf -------------------------------------------------------------------------------- /examples/results/0.21-UMD.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.21-UMD.pdf -------------------------------------------------------------------------------- /examples/results/0.22-SonyAIBORobotSurface2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.22-SonyAIBORobotSurface2.pdf -------------------------------------------------------------------------------- /examples/results/0.23-PickupGestureWiimoteZ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.23-PickupGestureWiimoteZ.pdf -------------------------------------------------------------------------------- /examples/results/0.24-GestureMidAirD1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.24-GestureMidAirD1.pdf -------------------------------------------------------------------------------- /examples/results/0.25-ECG200.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.25-ECG200.pdf -------------------------------------------------------------------------------- /examples/results/0.26-DistalPhalanxTW.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.26-DistalPhalanxTW.pdf -------------------------------------------------------------------------------- /examples/results/0.26-ShakeGestureWiimoteZ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.26-ShakeGestureWiimoteZ.pdf -------------------------------------------------------------------------------- /examples/results/0.27-GestureMidAirD2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.27-GestureMidAirD2.pdf -------------------------------------------------------------------------------- /examples/results/0.27-MelbournePedestrian.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.27-MelbournePedestrian.pdf -------------------------------------------------------------------------------- /examples/results/0.34-ArrowHead.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.34-ArrowHead.pdf -------------------------------------------------------------------------------- /examples/results/0.34-BeetleFly.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.34-BeetleFly.pdf -------------------------------------------------------------------------------- /examples/results/0.35-ShapesAll.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.35-ShapesAll.pdf -------------------------------------------------------------------------------- /examples/results/0.36-GesturePebbleZ1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.36-GesturePebbleZ1.pdf -------------------------------------------------------------------------------- /examples/results/0.36-ToeSegmentation1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.36-ToeSegmentation1.pdf -------------------------------------------------------------------------------- /examples/results/0.37-SyntheticControl.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.37-SyntheticControl.pdf -------------------------------------------------------------------------------- /examples/results/0.37-ToeSegmentation2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.37-ToeSegmentation2.pdf -------------------------------------------------------------------------------- /examples/results/0.39-GesturePebbleZ2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.39-GesturePebbleZ2.pdf -------------------------------------------------------------------------------- /examples/results/0.39-GunPointMaleVersusFemale.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.39-GunPointMaleVersusFemale.pdf -------------------------------------------------------------------------------- /examples/results/0.40-SwedishLeaf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.40-SwedishLeaf.pdf -------------------------------------------------------------------------------- /examples/results/0.42-MiddlePhalanxOutlineAgeGroup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.42-MiddlePhalanxOutlineAgeGroup.pdf -------------------------------------------------------------------------------- /examples/results/0.42-ProximalPhalanxTW.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.42-ProximalPhalanxTW.pdf -------------------------------------------------------------------------------- /examples/results/0.43-InsectEPGRegularTrain.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.43-InsectEPGRegularTrain.pdf -------------------------------------------------------------------------------- /examples/results/0.44-InsectEPGSmallTrain.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.44-InsectEPGSmallTrain.pdf -------------------------------------------------------------------------------- /examples/results/0.44-MoteStrain.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.44-MoteStrain.pdf -------------------------------------------------------------------------------- /examples/results/0.51-ProximalPhalanxOutlineAgeGroup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.51-ProximalPhalanxOutlineAgeGroup.pdf -------------------------------------------------------------------------------- /examples/results/0.55-Fish.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.55-Fish.pdf -------------------------------------------------------------------------------- /examples/results/0.56-FaceFour.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.56-FaceFour.pdf -------------------------------------------------------------------------------- /examples/results/0.56-FreezerRegularTrain.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.56-FreezerRegularTrain.pdf -------------------------------------------------------------------------------- /examples/results/0.58-Meat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.58-Meat.pdf -------------------------------------------------------------------------------- /examples/results/0.58-MiddlePhalanxTW.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.58-MiddlePhalanxTW.pdf -------------------------------------------------------------------------------- /examples/results/0.75-Symbols.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.75-Symbols.pdf -------------------------------------------------------------------------------- /examples/results/0.76-DodgerLoopWeekend.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.76-DodgerLoopWeekend.pdf -------------------------------------------------------------------------------- /examples/results/0.76-OliveOil.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.76-OliveOil.pdf -------------------------------------------------------------------------------- /examples/results/0.77-TwoLeadECG.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.77-TwoLeadECG.pdf -------------------------------------------------------------------------------- /examples/results/0.78-DiatomSizeReduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.78-DiatomSizeReduction.pdf -------------------------------------------------------------------------------- /examples/results/0.79-Coffee.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.79-Coffee.pdf -------------------------------------------------------------------------------- /examples/results/0.81-CBF.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.81-CBF.pdf -------------------------------------------------------------------------------- /examples/results/0.82-Chinatown.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.82-Chinatown.pdf -------------------------------------------------------------------------------- /examples/results/0.90-Fungi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.90-Fungi.pdf -------------------------------------------------------------------------------- /examples/results/0.99-Plane.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.99-Plane.pdf -------------------------------------------------------------------------------- /examples/results/0.99-Trace.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/examples/results/0.99-Trace.pdf -------------------------------------------------------------------------------- /examples/results/README.md: -------------------------------------------------------------------------------- 1 | # Example of k-Graph on the UCR-Archive datasets 2 | 3 | In each file, we show: 4 | 1. The dataset (each class seperated). In blue is one example time series of the dataset (one per class). 5 | 2. The selected graph (with gamma graphoids with gamma equal to 0.6). 6 | 3. The consistency and the interpretability factor for each length. 7 | 4. The 4 most representative and exclusive nodes per cluster. 8 | 9 | The ARI obtained is in the name of the file. 10 | 11 | Please note that k-Graph is non-derterministic (the length are randomly chosen at each run). Therefore, two run of k-Graph can lead to differnt results. Moreover, the graph layout is also non-derterministic. As a consequence, the aspect of the graph might be different, not only because of different clustering results but also because of a different layout for the graph plot. 12 | -------------------------------------------------------------------------------- /examples/scripts/Trace_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pandas as pd 3 | import numpy as np 4 | import networkx as nx 5 | import matplotlib.pyplot as plt 6 | from sklearn.metrics import adjusted_rand_score 7 | 8 | sys.path.insert(1, '../../utils/') 9 | from utils import fetch_ucr_dataset_online 10 | 11 | from kgraph import kGraph 12 | 13 | 14 | 15 | if __name__ == '__main__': 16 | 17 | X, y = fetch_ucr_dataset_online("Coffee") 18 | # Executing kGraph 19 | 20 | clf = kGraph(n_clusters=len(set(y)),n_lengths=10,n_jobs=4) 21 | clf.fit(X) 22 | 23 | print("Selected length: ",clf.optimal_length) 24 | print("ARI score: ",adjusted_rand_score(clf.labels_,y)) 25 | 26 | # Plotgraphoid 27 | 28 | clf.show_graphoids(group=True,save_fig=True,namefile='Trace_kgraph') 29 | 30 | # Plot representative patterns for each cluster 31 | 32 | nb_patterns = 1 33 | 34 | nodes = clf.interprete(nb_patterns=nb_patterns) 35 | 36 | plt.figure(figsize=(10,4*nb_patterns)) 37 | count = 0 38 | for j in range(nb_patterns): 39 | for i,node in enumerate(nodes.keys()): 40 | count += 1 41 | plt.subplot(nb_patterns,len(nodes.keys()),count) 42 | mean,sup,inf = clf.get_node_ts(X=X,node=nodes[node][j][0]) 43 | plt.fill_between(x=list(range(int(clf.optimal_length))),y1=inf,y2=sup,alpha=0.2) 44 | plt.plot(mean,color='black') 45 | plt.plot(inf,color='black',alpha=0.6,linestyle='--') 46 | plt.plot(sup,color='black',alpha=0.6,linestyle='--') 47 | plt.title('node {} for cluster {}: \n (representativity: {:.3f} \n exclusivity : {:.3f})'.format(nodes[node][j][0],node,nodes[node][j][3],nodes[node][j][2])) 48 | plt.tight_layout() 49 | 50 | plt.savefig('Trace_cluster_interpretation.jpg') 51 | plt.close() 52 | 53 | -------------------------------------------------------------------------------- /kgraph/__init__.py: -------------------------------------------------------------------------------- 1 | #__init__.py 2 | 3 | __version__ = "0.0.1" 4 | __author__ = 'Paul Boniol' 5 | 6 | from .kgraph_core import kGraph -------------------------------------------------------------------------------- /kgraph/kgraph_core.py: -------------------------------------------------------------------------------- 1 | import os 2 | import math 3 | import time 4 | import random 5 | import collections 6 | import numpy as np 7 | import pandas as pd 8 | import matplotlib 9 | import matplotlib.pyplot as plt 10 | from .utils import LIST_COLOR 11 | 12 | 13 | import scipy 14 | from scipy.stats import norm 15 | from scipy.stats import gaussian_kde 16 | from scipy.signal import argrelextrema 17 | 18 | from multiprocessing import Pool,get_context 19 | 20 | from sklearn.decomposition import PCA 21 | from sklearn.cluster import KMeans,SpectralClustering 22 | from sklearn.metrics import adjusted_rand_score 23 | from sklearn.preprocessing import StandardScaler 24 | 25 | import networkx as nx 26 | 27 | #from networkx.drawing.nx_agraph import graphviz_layout 28 | 29 | 30 | 31 | 32 | class kGraph(object): 33 | """ 34 | kGraph method for time series clustering. 35 | 36 | Parameters 37 | ---------- 38 | n_clusters : int, optional 39 | Number of clusters (default is 2). 40 | 41 | n_lengths : int, optional 42 | Number of lengths (default is 10). 43 | The lengths are selected between min_length and the time series length 44 | multiplied by rate_max_length. 45 | 46 | n_jobs : int, optional 47 | Number of jobs tun run in parallel the graph computation for each length 48 | (default is 1). 49 | 50 | rate : int, optional 51 | Number of radius for the radial scan of the node creation step (default is 30). 52 | This parameter controls the number of nodes per graph. A low rate will 53 | limit the number of nodes. 54 | 55 | min_length : int, optional 56 | Minimum length (default is 10). 57 | 58 | rate_max_length : float, optional 59 | Rate defining the maximum length (default is 0.33). 60 | This rate is multiplied by the time series lengths. 61 | 62 | seed : int, optional 63 | Seed for reproducibility (default is 0). 64 | 65 | sample : int, optional 66 | Sample parameter to train the PCA of the projection step (default is 10). 67 | 68 | variable_length : bool, optional 69 | If True, X is expected to be a list of arrays of differnt lengths (default is False). 70 | If False, X is expected to be an array. 71 | 72 | precompute_explaination: bool, optional 73 | If True, precompute optimal length used for interpretation of the clustering (default False). 74 | If False, optimal length will be computed at each explanation/interpretation. 75 | Recommended to set to True if an interpretation/explanation is desired. 76 | Recommended to set to False if only the clustering results is needed. 77 | 78 | verbose : bool, optional 79 | If True, print verbose information during execution (default is True). 80 | 81 | Attributes 82 | ---------- 83 | 84 | labels_ : ndarray of shape (n_samples,) 85 | Labels of each time series 86 | 87 | all_lengths : ndarray of shape (n_lengths,) 88 | The selected length. The lengths are contained between min_length and 89 | the time series length multiplied by rate_max_length. 90 | 91 | length_relevance : ndarray of shape (2,n_lengths) 92 | Relevance of each length according to the consensus clustering. 93 | In practice, the relevance is computed by measuring the Adjusted Rand Index 94 | between the consensus clustering and the kmeans clustering from the graph 95 | corresponding to the length. 96 | 97 | graph_relevance : ndarray of shape (2,n_lengths) 98 | Relevance of each length according to the corresponding graph interpretability. 99 | 100 | relevance : ndarray of shape (2,n_lengths) 101 | Relevance of each length according to the corresponding graph interpretability 102 | and the consensus clustering. Relevance is the element-wise product between 103 | the length_relevance and the graph_relevance. 104 | 105 | optimal_length : int 106 | The optimal length based on the relevance 107 | 108 | graphs : dict with n_lengths as keys 109 | information stored for each graph. Each key/length is associated to a dict 110 | with the following keys: 111 | 112 | - graph: dict containing information on the graph, with the following keys: 113 | - list_edge : List of successive edges. 114 | 115 | - dict_edge : dict with the edges as keys and the number of times the 116 | edges have been crossed as values. 117 | 118 | - dict_node : dict with the nodes as keys and the number of times the 119 | nodes have been crossed as values. 120 | 121 | - list_edge_pos : list of int with a length equal to n_samples. 122 | list_edge_pos[i] and list_edge_pos[i+1] corresponds to the position 123 | in list_edge when the time series i starts and ends. 124 | 125 | - edge_in_time : list of lists with a length equal to n_samples 126 | Each list corresponds to one time series T and has the same length 127 | as T. The position edge_in_time[i][j] corresponds to the position 128 | in list_edge at the timestamp j in the time series i. 129 | 130 | - prediction: ndarray of shape (n_samples,) 131 | Labels of each time series according to the graph associated to the 132 | length (i.e., the key). 133 | 134 | - feature : pandas DataFrame (n_samples, n_nodes+n_edges) 135 | DataFrame containing the feature from the graph (used for the first 136 | clustering step) for each time series. 137 | """ 138 | def __init__(self, 139 | n_clusters=2, 140 | n_lengths=10, 141 | n_jobs=1, 142 | rate=30, 143 | min_length=10, 144 | rate_max_length=0.33, 145 | seed=0, 146 | sample=10, 147 | variable_length = False, 148 | precompute_explaination = False, 149 | verbose=True): 150 | """ 151 | initialize kGraph method 152 | """ 153 | self.n_clusters = n_clusters 154 | self.n_lengths = n_lengths 155 | self.n_jobs = n_jobs 156 | 157 | self.rate = rate 158 | self.min_length = min_length 159 | self.rate_max_length = rate_max_length 160 | self.seed = seed 161 | self.verbose = verbose 162 | self.sample = sample 163 | self.variable_length = variable_length 164 | self.compute_revelance = precompute_explaination 165 | 166 | self.optimal_length = None 167 | 168 | # Public method 169 | 170 | def fit(self,X,y=None): 171 | """ 172 | compute kGraph on X 173 | 174 | Parameters 175 | ---------- 176 | X : array of shape (n_samples, n_timestamps) 177 | Training instances to cluster. 178 | 179 | Returns 180 | ------- 181 | self : object 182 | Fitted estimator. 183 | """ 184 | length = min([len(x) for x in X]) 185 | random_length_set = list( 186 | set( 187 | [int(val) for val in set( 188 | np.linspace( 189 | self.min_length, 190 | max( 191 | self.min_length, 192 | int(length*self.rate_max_length) 193 | ), 194 | self.n_lengths 195 | ) 196 | ) 197 | ] 198 | ) 199 | ) 200 | 201 | self.__verboseprint("Running kGraph for the following length: {}".format(random_length_set)) 202 | 203 | tim_start = time.time() 204 | 205 | if self.sample > (len(X)*(len(X[0]) - max(random_length_set)))//3: 206 | self.sample = (len(X)*(len(X[0]) - max(random_length_set)))//3 207 | self.__verboseprint("[WARNING]: Sample too large. Setting to the maximum acceptable value: {}".format(self.sample)) 208 | 209 | parameters = [[X,pattern_length] for pattern_length in random_length_set] 210 | with get_context("spawn").Pool(processes=self.n_jobs) as pool: 211 | all_pred_raw = pool.map(self.run_graphs_parallel,parameters) 212 | 213 | all_pred,all_graph,all_df,all_pattern = [],[],[],[] 214 | 215 | for pred in all_pred_raw: 216 | all_pred.append(np.array(pred[0])) 217 | all_graph.append(pred[1]) 218 | all_df.append(pred[2]) 219 | all_pattern.append(pred[3]) 220 | 221 | self.__verboseprint("Graphs computation done! ({} s)".format(time.time() - tim_start)) 222 | 223 | tim_start = time.time() 224 | sim_matrix = self.__build_consensus_matrix(all_pred) 225 | 226 | self.__verboseprint("Consensus done! ({} s)".format(time.time() - tim_start)) 227 | 228 | tim_start = time.time() 229 | 230 | clustering_ens = SpectralClustering( 231 | n_clusters=self.n_clusters, 232 | assign_labels='discretize', 233 | affinity='precomputed', 234 | random_state=self.seed, 235 | n_jobs=self.n_jobs).fit(sim_matrix) 236 | 237 | self.__verboseprint("Ensemble clustering done! ({} s)".format(time.time() - tim_start)) 238 | 239 | self.graphs = {all_pattern[i]: 240 | {'graph':all_graph[i], 241 | 'prediction':all_pred[i], 242 | 'feature':all_df[i], 243 | } for i in range(len(random_length_set))} 244 | self.graphs = collections.OrderedDict(sorted(self.graphs.items())) 245 | self.all_lengths = list(self.graphs.keys()) 246 | self.labels_ = clustering_ens.labels_ 247 | 248 | 249 | if self.compute_revelance == True: 250 | self.__get_length_relevance() 251 | 252 | return self 253 | 254 | 255 | def compute_graphoids(self,length=None,mode='Exclusive',majority_level=0.8): 256 | """ 257 | Extract graphoid for a given length. More precisely, the graphoids associated to 258 | each node and edge the number of time series of a given cluster that crossed it. 259 | 260 | Parameters 261 | ---------- 262 | length : int or None, optional 263 | Length parameter for graphoid computation (default is None). 264 | If None, length is set to self.optimal_length. 265 | 266 | mode : {'Raw','Exclusive', 'Proportion'}, optional 267 | Mode for computing graphoids. If 'Exclusive', compute exclusive graphoids 268 | (i.e., set to zero the nodes and edges that are not above majority_level). 269 | If 'Proportion', compute graphoids and normalize the value such that, for one node, 270 | the sum for all graphoids is equal to one. (default is 'Exclusive'). 271 | 272 | majority_level : float, optional 273 | Majority level threshold for graphoid computation (default is 0.8). 274 | Not used for Proportion mode 275 | 276 | Returns 277 | ------- 278 | result : ndarray of shape (n_clusters,n_nodes + n_edges) 279 | Graphoids for each cluster. 280 | 281 | """ 282 | if length is None: 283 | if self.optimal_length is None: 284 | self.__get_length_relevance() 285 | length = self.optimal_length 286 | 287 | all_graphoid,names_features = self.__compute_all_graphoid(length) 288 | if mode == 'Raw': 289 | pass 290 | elif mode == 'Exclusive': 291 | all_graphoid = self.__compute_all_exclusive_graphoid(all_graphoid,majority_level=majority_level) 292 | elif mode == 'Proportion': 293 | all_graphoid = self.__compute_all_prop_graphoid(all_graphoid,length,names_features,majority_level=0.8) 294 | 295 | return np.array(all_graphoid),names_features 296 | 297 | 298 | def get_node_ts(self,X,node,length=None): 299 | """ 300 | For a given node (for a given dataset X), compute the representative time series of the node. 301 | X has to be the same as the one used in the fit method. 302 | 303 | Parameters 304 | ---------- 305 | X : array of shape (n_samples, n_timestamps) 306 | Instances used to train kGraph in the fit method. 307 | 308 | node : str 309 | Must be be an existing node in self.graphs[length]['graph']['dict_node'] 310 | 311 | length : int or None, optional 312 | Length parameter for graphoid computation (default is None). 313 | If None, length is set to self.optimal_length. 314 | 315 | Returns 316 | ------- 317 | result : three 1D arrays of shape (length) 318 | return the average representative, the upper-bound (mean + std) 319 | and the lower bound (mean - std). Note that the returned time series 320 | is normalized. 321 | 322 | """ 323 | if length is None: 324 | if self.optimal_length is None: 325 | self.__get_length_relevance() 326 | length = self.optimal_length 327 | 328 | result = [] 329 | 330 | global_pos = [] 331 | current_pos = 0 332 | 333 | edge_in_time = self.graphs[length]['graph']['edge_in_time'] 334 | for i,edge in enumerate(self.graphs[length]['graph']['list_edge']): 335 | 336 | if node == edge[0]: 337 | relative_pos = i-self.graphs[length]['graph']['list_edge_pos'][current_pos] 338 | pos_in_time = min( 339 | range(len(edge_in_time[current_pos])), 340 | key=lambda j: abs(edge_in_time[current_pos][j]-relative_pos)) 341 | if self.variable_length: 342 | ts = X[int(current_pos)][int(pos_in_time):int(pos_in_time+length)] 343 | else: 344 | ts = X[int(current_pos),int(pos_in_time):int(pos_in_time+length)] 345 | ts = ts - np.mean(ts) 346 | result.append(ts) 347 | 348 | if i >= self.graphs[length]['graph']['list_edge_pos'][current_pos+1]: 349 | current_pos += 1 350 | 351 | mean = np.mean(result,axis=0) 352 | dev = np.std(result,axis=0) 353 | 354 | return mean,mean-dev,mean+dev 355 | 356 | 357 | def interprete(self,length=None,nb_patterns=1): 358 | """ 359 | Return the nb_patterns most representative nodes (i.e., the nodes 360 | that have been crossed by most of (and only) time series of a 361 | given cluster). 362 | 363 | Parameters 364 | ---------- 365 | length : int or None, optional 366 | Length parameter for graphoid computation (default is None). 367 | If None, length is set to self.optimal_length. 368 | 369 | nb_patterns: int, optional (default is 1) 370 | Number of nodes to return. 371 | 372 | Returns 373 | ------- 374 | result : dict with the cluster labels as keys. 375 | Each cluster C is associated with a list of nb_patterns nodes 376 | (sorted by their representativeness and effectiveness). 377 | Each node is a list with the following elements: 378 | 379 | - Node name: str 380 | name of the node in the graph associated with length. 381 | 382 | - Importance score: float (between 0 and 1) 383 | Exclusivity and Representativity score multiplied. 384 | 385 | - Exclusivity score: float (between 0 and 1) 386 | Percentage of time series crossing the node that belongs to cluster C. 387 | 388 | - Representativity score: float (between 0 and 1) 389 | Percentage of time series of cluster C crossing the node. 390 | 391 | """ 392 | if length is None: 393 | if self.optimal_length is None: 394 | self.__get_length_relevance() 395 | length = self.optimal_length 396 | 397 | all_graphoid,names_features = self.compute_graphoids(length=length,mode='Proportion') 398 | names_features = np.array(names_features) 399 | 400 | 401 | nodes_name = [] 402 | for i in range(len(names_features)): 403 | if ('[' not in names_features[i]): 404 | nodes_name.append(i) 405 | 406 | 407 | cluster_interpretation = {} 408 | for cluster_x in set(self.labels_): 409 | norm_graph = all_graphoid[cluster_x] 410 | 411 | set_nodes = [] 412 | nodes_exclusivity = [] 413 | 414 | max_exp_node = 0.8 415 | 416 | for val,name in zip(norm_graph[nodes_name],names_features[nodes_name]): 417 | #if val > max_exp_node: 418 | set_nodes.append(name) 419 | nodes_exclusivity.append(val) 420 | 421 | nodes_representativity = self.__compute_representativity_node(length,set_nodes,cluster_x) 422 | 423 | cluster_interpretation[cluster_x] = sorted([ 424 | [node,exc*rep,exc,rep] for node,exc,rep in zip(set_nodes,nodes_exclusivity,nodes_representativity) 425 | ],key=lambda tup: tup[1],reverse=True)[:nb_patterns] 426 | return cluster_interpretation 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | def explain(self,i_x,length=None): 435 | """ 436 | compute the local explanation curve for time series i_x in X. 437 | X is the same as the one used in the fit method. 438 | 439 | Parameters 440 | ---------- 441 | i_x : int. 442 | Index of the time series to explain in X. 443 | 444 | length : int or None, optional 445 | Length parameter for graphoid computation (default is None). 446 | If None, length is set to self.optimal_length. 447 | 448 | Returns 449 | ------- 450 | result : 1D array of shape (X[i_x].shape[1],) 451 | return a time series (same length as the time series i_x in X). 452 | A high value in the latter indicates that the corresponding position in T 453 | is a representative element of T of belonging to cluster self.labels_[i_x]. 454 | 455 | """ 456 | if length is None: 457 | if self.optimal_length is None: 458 | self.__get_length_relevance() 459 | length = self.optimal_length 460 | 461 | 462 | edge_in_time = self.graphs[length]['graph']['edge_in_time'][i_x] 463 | edges_start = self.graphs[length]['graph']['list_edge_pos'][i_x] 464 | edges_end = self.graphs[length]['graph']['list_edge_pos'][i_x+1] 465 | edges = self.graphs[length]['graph']['list_edge'][edges_start:edges_end] 466 | 467 | random_prob = 1.0/float(self.n_clusters) 468 | cluster_x = self.labels_[i_x] 469 | all_graphoid,names_features = self.compute_graphoids(length=length,mode='Proportion') 470 | 471 | all_nodes = [[]] 472 | all_edges = [[]] 473 | for i in range(1,len(edge_in_time)): 474 | edges_seq = edges[edge_in_time[i-1]:edge_in_time[i]] 475 | if len(edges_seq) > 0: 476 | nodes_seq = [e[0] for e in edges_seq] + [edges_seq[-1][1]] 477 | else: 478 | if len(all_nodes[-1]) > 0: 479 | nodes_seq = [all_nodes[-1][-1]] 480 | else: 481 | nodes_seq = [] 482 | all_nodes.append(nodes_seq) 483 | all_edges.append(edges_seq) 484 | 485 | norm_graph = all_graphoid[cluster_x] 486 | 487 | all_node_relevance,all_edge_relevance = [],[] 488 | 489 | for i,(nodes_seq,edges_seq) in enumerate(zip(all_nodes,all_edges)): 490 | if len(nodes_seq) > 0: 491 | node_relevance = np.mean([norm_graph[names_features.index(node)] for node in nodes_seq]) 492 | else: 493 | node_relevance = random_prob 494 | if len(edges_seq) > 0: 495 | edge_relevance = np.mean([norm_graph[names_features.index(str(edge))] for edge in edges_seq]) 496 | else: 497 | edge_relevance = random_prob 498 | 499 | all_node_relevance.append(node_relevance) 500 | all_edge_relevance.append(edge_relevance) 501 | 502 | return np.max([all_node_relevance,all_edge_relevance],axis=0) 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | # Private method for kGraph computation 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | def __compute_representativity_node(self,length,list_elem,label): 520 | 521 | res = {elem:0 for elem in list_elem} 522 | 523 | tot_ts = len(self.graphs[length]['graph']['list_edge_pos'])-1 524 | 525 | cluster_prop = list(self.labels_).count(label) 526 | 527 | for i in range(tot_ts): 528 | if self.labels_[i] == label: 529 | edges_start = self.graphs[length]['graph']['list_edge_pos'][i] 530 | edges_end = self.graphs[length]['graph']['list_edge_pos'][i+1] 531 | edges = self.graphs[length]['graph']['list_edge'][edges_start:edges_end] 532 | 533 | if len(edges) > 0: 534 | nodes = set([e[0] for e in edges] + [edges[-1][1]]) 535 | else: 536 | nodes = [] 537 | 538 | for elem in list_elem: 539 | if elem in nodes: 540 | res[elem] += 1/cluster_prop 541 | 542 | return list(res.values()) 543 | 544 | 545 | def __get_length_relevance(self): 546 | 547 | self.length_relevance = [] 548 | self.graph_relevance = [] 549 | for length in self.graphs.keys(): 550 | all_graphoid,names_features = self.compute_graphoids(length=length,mode='Proportion') 551 | self.length_relevance.append( 552 | adjusted_rand_score(self.graphs[length]['prediction'],self.labels_)) 553 | self.graph_relevance.append(np.mean(np.max(all_graphoid[:,self.__get_node(names_features)],axis=1))) 554 | 555 | self.relevance = np.array(self.length_relevance) * np.array(self.graph_relevance) 556 | 557 | self.length_relevance = np.array([[l,val]for l,val in zip(self.graphs.keys(),self.length_relevance)]) 558 | self.graph_relevance = np.array([[l,val]for l,val in zip(self.graphs.keys(),self.graph_relevance)]) 559 | self.relevance = np.array([[l,val]for l,val in zip(self.graphs.keys(),self.relevance)]) 560 | 561 | self.optimal_length = self.relevance[np.argmax(self.relevance,axis=0)[1]][0] 562 | 563 | def __get_node(self,feature_names): 564 | res = [] 565 | for i,name in enumerate(feature_names): 566 | if '[' not in name: 567 | res.append(i) 568 | return res 569 | 570 | def __compute_all_exclusive_graphoid(self,all_graphoid,majority_level=0.8): 571 | all_graphoid_exclusive = [] 572 | for i,graphoid in enumerate(all_graphoid): 573 | tmp = np.sum(all_graphoid,axis=0) 574 | result = [] 575 | for c,val in zip(graphoid,tmp): 576 | if c/(val+0.0001) > majority_level: 577 | result.append(2*c - val) 578 | else: 579 | result.append(0) 580 | all_graphoid_exclusive.append(result) 581 | return all_graphoid_exclusive 582 | 583 | 584 | def __compute_all_prop_graphoid(self,all_graphoid,length,names_features,majority_level=0.8): 585 | all_graphoid_prop = [] 586 | for i,graphoid in enumerate(all_graphoid): 587 | tmp = np.sum(all_graphoid,axis=0) 588 | result = [] 589 | for c,val,name in zip(graphoid,tmp,names_features): 590 | result.append(c/(val+0.0001)) 591 | all_graphoid_prop.append(result) 592 | return all_graphoid_prop 593 | 594 | def __compute_all_graphoid(self,length): 595 | all_graphoid = [] 596 | for cluster in set(self.labels_): 597 | graphoid,names_features = self.__compute_graphroid(length,cluster) 598 | all_graphoid.append(graphoid) 599 | return all_graphoid,names_features 600 | 601 | def __compute_graphroid(self,length,cluster): 602 | features = self.graphs[length]['feature'] 603 | data = [] 604 | for i in range(len(self.labels_)): 605 | if cluster == self.labels_[i]: 606 | data.append(list(features.values[i])) 607 | data = np.array(data) 608 | return np.mean(data,0),list(features.columns) 609 | 610 | def run_graphs_parallel(self,args): 611 | 612 | return self.__run_clustering_length(*args) 613 | 614 | def __create_dataset(self,G,X): 615 | df_node = pd.DataFrame( 616 | index = list(range(len(X))), 617 | columns=list(G['dict_node'].keys())) 618 | df_edge = pd.DataFrame( 619 | index = list(range(len(X))), 620 | columns=[str(edge) for edge in list(G['dict_edge'].keys())]) 621 | df_conf = pd.DataFrame( 622 | index = list(range(len(X))), 623 | columns=list(G['dict_node'].keys())) 624 | df_node = df_node.fillna(0) 625 | df_edge = df_edge.fillna(0) 626 | df_conf = df_conf.fillna(0) 627 | for pos_edge_index in range(len(G['list_edge_pos'])-1): 628 | edge_to_analyse = G['list_edge'][G['list_edge_pos'][pos_edge_index]:G['list_edge_pos'][pos_edge_index+1]] 629 | G_nx = nx.DiGraph(edge_to_analyse) 630 | degree_to_anaylse = {node:val for (node,val) in G_nx.degree()} 631 | for edge in edge_to_analyse: 632 | df_node.at[pos_edge_index,edge[0]] += 1 633 | df_conf.at[pos_edge_index,edge[0]] = degree_to_anaylse[edge[0]] 634 | df_edge.at[pos_edge_index,str(edge)] += 1 635 | return df_node,df_edge,df_conf 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | def __create_membership_matrix(self,run): 644 | mat = np.zeros((len(run),len(run))) 645 | for i,val_i in enumerate(run): 646 | for j,val_j in enumerate(run): 647 | if val_i == val_j: 648 | mat[i][j] = 1 649 | mat[j][i] = 1 650 | return mat 651 | 652 | def __build_consensus_matrix(self,all_runs): 653 | all_mat = sum([self.__create_membership_matrix(run) for run in all_runs]) 654 | return all_mat/all_mat.diagonal() 655 | 656 | def __Clustering_df(self,df): 657 | Method = KMeans(n_clusters=self.n_clusters, random_state=0, n_init=1) 658 | df_normalized=df 659 | df_normalized = df_normalized.subtract( 660 | df_normalized.mean(axis=1),axis=0).div(df_normalized.std(axis=1), 661 | axis=0) 662 | df_normalized = df_normalized.fillna(0) 663 | 664 | 665 | 666 | kmeans = Method.fit(df_normalized.values) 667 | return kmeans.labels_ 668 | 669 | 670 | 671 | def __run_clustering_length(self,X,pattern_length): 672 | G = self.__create_graph( 673 | X=X, 674 | length_pattern=max(pattern_length,4), 675 | latent=max(1,pattern_length//3), 676 | rate=self.rate) 677 | df_node,df_edge,df_conf = self.__create_dataset(G,X) 678 | 679 | clustering_pred = self.__Clustering_df(pd.concat([df_node, df_edge,df_conf], axis=1)) 680 | 681 | return [clustering_pred,G,pd.concat([df_node, df_edge], axis=1),pattern_length] 682 | 683 | 684 | def __create_graph(self,X,length_pattern,latent,rate): 685 | 686 | dict_result_P = self.__run_proj(X,length_pattern,latent) 687 | dict_result_G = self.__create_graph_from_proj(dict_result_P,rate,length_pattern) 688 | 689 | return dict_result_G 690 | 691 | 692 | def __create_graph_from_proj(self,dict_result,rate,length_pattern): 693 | 694 | res_point,res_dist = self.__get_intersection_from_radius(dict_result['A'],dict_result['index_pos'],rate=rate) 695 | nodes_set,node_weight = self.__nodes_extraction(dict_result['A'],res_point,res_dist,rate=rate,pattern_length=length_pattern) 696 | 697 | dict_edge_all,dict_node_all = {},{} 698 | list_edge_all,edge_in_time_all,list_edge_pos = [],[],[] 699 | 700 | for i in range(len(dict_result['index_pos'])-1): 701 | sub_A = dict_result['A'][dict_result['index_pos'][i]:dict_result['index_pos'][i+1]] 702 | list_edge,edge_in_time,dict_edge,dict_node = self.__edges_extraction(sub_A,nodes_set,rate=rate) 703 | list_edge_pos.append(len(list_edge_all)) 704 | list_edge_all += list_edge 705 | edge_in_time_all.append(edge_in_time) 706 | dict_edge_all = self.__merge_dict(dict_edge_all,dict_edge) 707 | dict_node_all = self.__merge_dict(dict_node_all,dict_node) 708 | list_edge_pos.append(len(list_edge_all)) 709 | return { 710 | 'list_edge': list_edge_all, 711 | 'dict_edge': dict_edge_all, 712 | 'dict_node': dict_node_all, 713 | 'list_edge_pos':list_edge_pos, 714 | 'edge_in_time': edge_in_time_all, 715 | } 716 | 717 | def __run_proj(self,X,length_pattern,latent): 718 | 719 | if self.variable_length: 720 | min_X = np.min([np.min(X_sub) for X_sub in X]) 721 | max_X = np.max([np.max(X_sub) for X_sub in X]) 722 | else: 723 | min_X = np.min(X) 724 | max_X = np.max(X) 725 | downsample = max(1,latent//100) 726 | 727 | 728 | 729 | 730 | X_ref = [] 731 | for i in np.arange(min_X, max_X,(max_X-min_X)/100): 732 | tmp = [] 733 | T = [i]*length_pattern 734 | for j in range(length_pattern - latent): 735 | tmp.append(sum(x for x in T[j:j+latent])) 736 | X_ref.append(tmp[::downsample]) 737 | X_ref = np.array(X_ref) 738 | 739 | phase_space_train_list = [] 740 | index_pos = [] 741 | current_length = 0 742 | for X_sub in X: 743 | to_add = self.__build_phase_space_smpl(X_sub,latent,length_pattern) 744 | index_pos.append(current_length) 745 | phase_space_train_list.append(to_add) 746 | current_length += len(to_add) 747 | 748 | if len(phase_space_train_list) == 1: 749 | phase_space_train = phase_space_train_list[0] 750 | else: 751 | phase_space_train = np.concatenate(phase_space_train_list,axis=0) 752 | 753 | sample = self.sample#max(1,latent//2)#4 754 | 755 | pca_1 = PCA(n_components=3,svd_solver='randomized').fit(phase_space_train[np.random.choice( 756 | len(phase_space_train), 757 | size=len(phase_space_train)//sample, 758 | replace=False)]) 759 | 760 | reduced = pca_1.transform(phase_space_train) 761 | reduced_ref = pca_1.transform(X_ref) 762 | 763 | v_1 = reduced_ref[0] 764 | 765 | R = self.__get_rotation_matrix(v_1,[0.0, 0.0, 1.0]) 766 | A = np.dot(R,reduced.T) 767 | A = A.T 768 | 769 | return { 770 | 'pca': pca_1, 771 | 'A': np.array(A)[:,0:2], 772 | 'R': R, 773 | 'index_pos':index_pos 774 | } 775 | 776 | 777 | def __build_phase_space_smpl(self,X_sub,latent,m): 778 | 779 | tmp_glob = [] 780 | downsample = max(1,latent//100) 781 | current_seq = [0]*m 782 | first = True 783 | for i in range(len(X_sub) - m): 784 | tmp = [] 785 | if first: 786 | first = False 787 | for j in range(m - latent): 788 | tmp.append(sum(x for x in X_sub[i+j:i+j+latent])) 789 | tmp_glob.append(tmp[::downsample]) 790 | current_seq = tmp 791 | else: 792 | tmp = current_seq[1:] 793 | tmp.append(sum(x for x in X_sub[i+m-latent:i+m])) 794 | tmp_glob.append(tmp[::downsample]) 795 | current_seq = tmp 796 | 797 | return np.array(tmp_glob) 798 | 799 | 800 | def __get_rotation_matrix(self,i_v, unit): 801 | 802 | curve_vec_1 = i_v 803 | curve_vec_2 = unit 804 | a = (curve_vec_1/ np.linalg.norm(curve_vec_1)).reshape(3) 805 | b = (curve_vec_2/ np.linalg.norm(curve_vec_2)).reshape(3) 806 | v = np.cross(a,b) 807 | c = np.dot(a,b) 808 | s = np.linalg.norm(v) 809 | I = np.identity(3) 810 | vXStr = '{} {} {}; {} {} {}; {} {} {}'.format( 811 | 0, -v[2], v[1], 812 | v[2], 0, -v[0], 813 | -v[1], v[0], 0) 814 | k = np.matrix(vXStr) 815 | r = I + k + k@k * ((1 -c)/(s**2)) 816 | 817 | return r 818 | 819 | 820 | def __find_theta_to_check(self,A,k,rate): 821 | k_0 = A[k,0] 822 | k_1 = A[k,1] 823 | k_1_0 = A[k+1,0] 824 | k_1_1 = A[k+1,1] 825 | dist_to_0 = np.sqrt(k_0**2 + k_1**2) 826 | dist_to_1 = np.sqrt(k_1_0**2 + k_1_1**2) 827 | theta_point = np.arctan2([k_1/dist_to_0],[k_0/dist_to_0])[0] 828 | theta_point_1 = np.arctan2([k_1_1/dist_to_1],[k_1_0/dist_to_1])[0] 829 | if theta_point < 0: 830 | theta_point += 2*np.pi 831 | if theta_point_1 < 0: 832 | theta_point_1 += 2*np.pi 833 | theta_point = int(theta_point/(2.0*np.pi) * (rate)) 834 | theta_point_1 = int(theta_point_1/(2.0*np.pi) * (rate)) 835 | diff_theta = abs(theta_point - theta_point_1) 836 | if diff_theta > rate//2: 837 | if theta_point_1 > rate//2: 838 | diff_theta = abs(theta_point - (-rate + theta_point_1)) 839 | elif theta_point > rate//2: 840 | diff_theta = abs((-rate + theta_point) - theta_point_1) 841 | diff_theta = min(diff_theta,rate//2) 842 | theta_to_check = [(theta_point + lag) % rate for lag in range(-diff_theta-1,diff_theta+1)] 843 | 844 | return theta_to_check 845 | 846 | 847 | def __distance(self,a,b): 848 | 849 | return np.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2) 850 | 851 | def __det(self,a, b): 852 | 853 | return a[0] * b[1] - a[1] * b[0] 854 | 855 | def __PointsInCircum(self,r,n=500): 856 | 857 | return np.array([[math.cos(2*np.pi/n*x)*r,math.sin(2*np.pi/n*x)*r] for x in range(0,n)]) 858 | 859 | def __line_intersection(self,line1, line2): 860 | 861 | xdiff = (line1[0,0] - line1[1,0], line2[0,0] - line2[1,0]) 862 | ydiff = (line1[0,1] - line1[1,1], line2[0,1] - line2[1,1]) 863 | 864 | div = self.__det(xdiff, ydiff) 865 | if div == 0: 866 | return None,None 867 | 868 | max_x_1 = max(line1[0,0],line1[1,0]) 869 | max_x_2 = max(line2[0,0],line2[1,0]) 870 | max_y_1 = max(line1[0,1],line1[1,1]) 871 | max_y_2 = max(line2[0,1],line2[1,1]) 872 | 873 | min_x_1 = min(line1[0,0],line1[1,0]) 874 | min_x_2 = min(line2[0,0],line2[1,0]) 875 | min_y_1 = min(line1[0,1],line1[1,1]) 876 | min_y_2 = min(line2[0,1],line2[1,1]) 877 | 878 | d = (self.__det(*line1), self.__det(*line2)) 879 | x = self.__det(d, xdiff) / div 880 | y = self.__det(d, ydiff) / div 881 | if not(((x <= max_x_1) and (x >= min_x_1)) and ((x <= max_x_2) and (x >= min_x_2))): 882 | return None,None 883 | if not(((y <= max_y_1) and (y >= min_y_1)) and ((y <= max_y_2) and (y >= min_y_2))): 884 | return None,None 885 | 886 | return [x, y], self.__distance(line1[0],[x,y]) 887 | 888 | def __get_intersection_from_radius(self,A,index_pos,rate): 889 | 890 | max_1 = max(max(A[:,0]),abs(min(A[:,0]))) 891 | max_2 = max(max(A[:,1]),abs(min(A[:,1]))) 892 | set_point = self.__PointsInCircum(np.sqrt(max_1**2 + max_2**2),n=rate) 893 | previous_node = "not_defined" 894 | 895 | result = [[] for i in range(len(set_point))] 896 | result_dist = [[] for i in range(len(set_point))] 897 | 898 | for k in random.sample(list(range(0,len(A)-1)),len(list(range(0,len(A)-1)))//self.sample): 899 | #if k-1 not in index_pos[1:]: 900 | theta_to_check = self.__find_theta_to_check(A,k,rate) 901 | was_found = False 902 | for i in theta_to_check: 903 | intersect,dist = self.__line_intersection( 904 | np.array([[0,0],set_point[i]]), 905 | np.array([A[k],A[k+1]])) 906 | if intersect is not None: 907 | was_found = True 908 | result[i].append(intersect) 909 | result_dist[i].append(dist) 910 | elif (was_found == True) and intersect is None: 911 | break 912 | new_result_dist = [] 913 | for i,res_d in enumerate(result_dist): 914 | new_result_dist += res_d 915 | 916 | return result,new_result_dist 917 | 918 | 919 | def __kde_scipy(self,x, x_grid): 920 | 921 | kde = gaussian_kde(x, bw_method='scott') 922 | 923 | return list(kde.evaluate(x_grid)) 924 | 925 | 926 | def __nodes_extraction(self,A,res_point,res_dist,rate,pattern_length): 927 | 928 | max_all = max(max(max(A[:,0]),max(A[:,1])),max(-min(A[:,0]),-min(A[:,1]))) 929 | max_all = max_all*1.2 930 | range_val_distrib = np.arange(0, max_all, max_all/250.0) 931 | list_maxima = [] 932 | list_maxima_val = [] 933 | for segment in range(rate): 934 | pos_start = sum(len(res_point[i]) for i in range(segment)) 935 | if len(res_dist[pos_start:pos_start+len(res_point[segment])]) == 0: 936 | self.__verboseprint("[WARNING] for Graph {}: No intersection found for at least one radius. Sample might be too high.".format(pattern_length)) 937 | maxima_ind = [0] 938 | maxima_val = [0] 939 | elif len(res_dist[pos_start:pos_start+len(res_point[segment])]) == 1: 940 | self.__verboseprint("[WARNING] for Graph {}: Few intersection found for at least one radius. Sample might be too high.".format(pattern_length)) 941 | maxima_ind = [res_dist[pos_start:pos_start+len(res_point[segment])][0]] 942 | maxima_val = [1] 943 | else: 944 | dist_on_segment = self.__kde_scipy( 945 | res_dist[pos_start:pos_start+len(res_point[segment])], 946 | range_val_distrib) 947 | dist_on_segment = (dist_on_segment - min(dist_on_segment))/(max(dist_on_segment) - min(dist_on_segment)) 948 | maxima = argrelextrema(np.array(dist_on_segment), np.greater)[0] 949 | if len(maxima) == 0: 950 | maxima = np.array([0]) 951 | maxima_ind = [range_val_distrib[val] for val in list(maxima)] 952 | maxima_val = [dist_on_segment[val] for val in list(maxima)] 953 | list_maxima.append(maxima_ind) 954 | list_maxima_val.append(maxima_val) 955 | 956 | return list_maxima,list_maxima_val 957 | 958 | def __find_closest_node(self,list_maxima_ind,point): 959 | 960 | result_list = [np.abs(maxi - point) for maxi in list_maxima_ind] 961 | result_list_sorted = sorted(result_list) 962 | 963 | return result_list.index(result_list_sorted[0]),result_list_sorted[0] 964 | 965 | def __find_tuple_interseted(self,A,line): 966 | 967 | result = [] 968 | dist_l = [] 969 | for i in range(len(A)-1): 970 | intersect,dist = self.__line_intersection(line, np.array([A[i],A[i+1]])) 971 | if intersect is not None: 972 | result.append(intersect) 973 | dist_l.append(dist) 974 | 975 | return [result,dist_l] 976 | 977 | 978 | def __edges_extraction(self,A,set_nodes,rate): 979 | 980 | list_edge = [] 981 | edge_in_time = [] 982 | dict_edge = {} 983 | dict_node = {} 984 | 985 | max_1 = max(max(A[:,0]),abs(min(A[:,0]))) 986 | max_2 = max(max(A[:,1]),abs(min(A[:,1]))) 987 | 988 | 989 | set_point = self.__PointsInCircum(np.sqrt(max_1**2 + max_2**2),n=rate) 990 | previous_node = "not_defined" 991 | 992 | 993 | for k in range(0,len(A)-1): 994 | 995 | theta_to_check = self.__find_theta_to_check(A,k,rate) 996 | was_found = False 997 | for i in theta_to_check: 998 | to_add = self.__find_tuple_interseted(A[k:k+2],np.array([[0,0],set_point[i]]))[1] 999 | if to_add == [] and not was_found: 1000 | continue 1001 | elif to_add == [] and was_found: 1002 | break 1003 | else: 1004 | was_found = True 1005 | node_in,distance = self.__find_closest_node(set_nodes[i],to_add[0]) 1006 | 1007 | if previous_node == "not_defined": 1008 | previous_node = "{}_{}".format(i,node_in) 1009 | dict_node[previous_node] = 1 1010 | 1011 | else: 1012 | list_edge.append([previous_node,"{}_{}".format(i,node_in)]) 1013 | 1014 | 1015 | if "{}_{}".format(i,node_in) not in dict_node.keys(): 1016 | dict_node["{}_{}".format(i,node_in)] = 1 1017 | else: 1018 | dict_node["{}_{}".format(i,node_in)] += 1 1019 | 1020 | if str(list_edge[-1]) in dict_edge.keys(): 1021 | dict_edge[str(list_edge[-1])] += 1 1022 | else: 1023 | dict_edge[str(list_edge[-1])] = 1 1024 | previous_node = "{}_{}".format(i,node_in) 1025 | 1026 | edge_in_time.append(len(list_edge)) 1027 | 1028 | 1029 | 1030 | return list_edge,edge_in_time,dict_edge,dict_node#,list_edge_dist 1031 | 1032 | 1033 | 1034 | def __merge_dict(self,dict_A,dict_B): 1035 | 1036 | new_dict = {} 1037 | for key in dict_A: 1038 | new_dict[key] = dict_A[key] 1039 | for key in dict_B: 1040 | if key in new_dict.keys(): 1041 | new_dict[key] += dict_B[key] 1042 | else: 1043 | new_dict[key] = dict_B[key] 1044 | 1045 | return new_dict 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | # Public methods for visualisation: 1059 | # 1060 | # Our visualisation methods are using Graphviz and pyGraphviz. 1061 | # Some users might have issues installing Graphviz. 1062 | # In case of problems installing PyGraphviz and Graphviz, please 1063 | # remove all functions below 1064 | 1065 | def show_graphs(self,lengths=None,figsize=(30,40), save_fig=False, namefile=None): 1066 | """ 1067 | Plot learned graphs for each length 1068 | """ 1069 | """ 1070 | Plot learned graphs for each length. 1071 | 1072 | Parameters 1073 | ---------- 1074 | length : list of int or None, optional 1075 | Lengths of graphs to plot (default is None). 1076 | If None, all graphs are plotted. 1077 | 1078 | savefig: Boolean, optional 1079 | if True, the figure is saved (with name namefile) 1080 | 1081 | namefile: str or None, optional 1082 | if savefig=True, the figure is saved with the name namefile 1083 | 1084 | Returns 1085 | ------- 1086 | None 1087 | """ 1088 | 1089 | try: 1090 | import pygraphviz 1091 | from networkx.drawing.nx_agraph import graphviz_layout 1092 | graph_viz_used = True 1093 | except ImportError as e: 1094 | print("[WARNING] pygrpaphviz not installed. Please install pygraphviz (and graphviz) for a more approriate graph visualization") 1095 | graph_viz_used = False 1096 | 1097 | if lengths is None: 1098 | all_lengths = list(self.graphs.keys()) 1099 | else: 1100 | all_lengths = lengths 1101 | 1102 | plt.figure(figsize=figsize) 1103 | for i,length in enumerate(all_lengths): 1104 | plt.subplot(int(len(all_lengths)/4)+1,4,1+i) 1105 | self.__plot_graph_length(length,graph_viz_used) 1106 | plt.title('Relevance of length {}: {:.3f}'.format( 1107 | length, 1108 | self.length_relevance[np.where(self.length_relevance == length)[0],1][0])) 1109 | 1110 | if save_fig: 1111 | if namefile is not None: 1112 | plt.savefig(namefile + '.jpg') 1113 | else: 1114 | print('[ERROR]: with save_fig=True, Please provide a namefile') 1115 | else: 1116 | plt.show() 1117 | plt.close() 1118 | 1119 | def show_graphoids(self,length=None,mode='Exclusive',group=False, 1120 | majority_level=0.8, figsize=(20,20), save_fig=False, namefile=None): 1121 | """ 1122 | Plot the graphoid for a specific length 1123 | 1124 | Parameters 1125 | ---------- 1126 | length : int or None, optional 1127 | Length parameter for graphoid computation (default is None). 1128 | If None, length is set to self.optimal_length. 1129 | 1130 | mode : {'Raw','Exclusive', 'Proportion'}, optional 1131 | Mode for computing graphoids. If 'Exclusive', compute exclusive graphoids 1132 | (i.e., set to zero the nodes and edges that are not above majority_level). 1133 | If 'Proportion', compute graphoids and normalize the value such that, for one node, 1134 | the sum for all graphoids is equal to one. (default is 'Exclusive'). 1135 | 1136 | majority_level : float, optional 1137 | Majority level threshold for graphoid computation (default is 0.8). 1138 | Not used for Proportion and Raw modes. 1139 | 1140 | group : Boolean, optional 1141 | if True, Plot each graphoid within an individual plot. Otherwise, plot all 1142 | graphoid within one plot (default is False). 1143 | 1144 | savefig: Boolean, optional 1145 | if True, the figure is saved (with name namefile) 1146 | 1147 | namefile: str or None, optional 1148 | if savefig=True, the figure is saved with the name namefile 1149 | 1150 | Returns 1151 | ------- 1152 | None 1153 | """ 1154 | 1155 | try: 1156 | import pygraphviz 1157 | from networkx.drawing.nx_agraph import graphviz_layout 1158 | graph_viz_used = True 1159 | except ImportError as e: 1160 | print("[WARNING] pygrpaphviz not installed. Please install pygraphviz (and graphviz) for a more approriate graph visualization") 1161 | graph_viz_used = False 1162 | 1163 | if length is None: 1164 | if self.optimal_length is None: 1165 | self.__get_length_relevance() 1166 | length = self.optimal_length 1167 | 1168 | G_nx = nx.DiGraph(self.graphs[length]['graph']['list_edge']) 1169 | if graph_viz_used: 1170 | pos = nx.nx_agraph.graphviz_layout(G_nx,prog="fdp") 1171 | else: 1172 | pos = nx.random_layout(G_nx) 1173 | 1174 | if group: 1175 | plt.figure(figsize=(10,10)) 1176 | self.__plot_graphoid(length,graphoid='all',mode=mode,pos=pos,majority_level=majority_level,graph_viz_used=graph_viz_used) 1177 | plt.title('All graphoids') 1178 | else: 1179 | plt.figure(figsize=figsize) 1180 | for i in range(len(set(self.labels_))): 1181 | plt.subplot(len(set(self.labels_))//2+1,2,i+1) 1182 | self.__plot_graphoid(length,graphoid=i,mode=mode,pos=pos,majority_level=majority_level,graph_viz_used=graph_viz_used) 1183 | plt.title('Graph (graphoid in red) for cluster {}'.format(i)) 1184 | 1185 | if save_fig: 1186 | if namefile is not None: 1187 | plt.savefig(namefile + '.jpg') 1188 | else: 1189 | print('[ERROR]: with save_fig=True, Please provide a namefile') 1190 | else: 1191 | plt.show() 1192 | plt.close() 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | # Private method visualisation 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | def __plot_graph_length(self,length,graph_viz_used=False): 1207 | G = self.graphs[length]['graph'] 1208 | G_nx = nx.DiGraph(self.graphs[length]['graph']['list_edge']) 1209 | if graph_viz_used: 1210 | pos = nx.nx_agraph.graphviz_layout(G_nx,prog="fdp") 1211 | else: 1212 | pos = nx.random_layout(G_nx) 1213 | G_label_0,dict_node_0,edge_size_0 = self.__format_graph_viz(G_nx,G['list_edge'],G['dict_node']) 1214 | nx.draw(G_label_0,pos=pos,node_size=dict_node_0,linewidths=1,width=edge_size_0) 1215 | nx.draw_networkx_labels(G_label_0, pos,font_size=10) 1216 | ax = plt.gca() 1217 | ax.collections[0].set_edgecolor("black") 1218 | 1219 | def __format_graph_viz(self,G,list_edge,node_weight): 1220 | edge_size = [] 1221 | 1222 | for edge in G.edges(): 1223 | edge_size.append(list_edge.count([edge[0],edge[1]])) 1224 | edge_size_b = [float(1+(e - min(edge_size)))/float(1+max(edge_size) - min(edge_size)) for e in edge_size] 1225 | edge_size = [min(e*10,5) for e in edge_size_b] 1226 | dict_node = [] 1227 | for node in G.nodes(): 1228 | if node != "NULL_NODE": 1229 | dict_node.append(max(100,node_weight[node])) 1230 | else: 1231 | dict_node.append(100) 1232 | 1233 | return G,dict_node,edge_size 1234 | 1235 | 1236 | def __plot_graphoid(self,length,graphoid='all',mode='Exclusive',pos=None,majority_level=0.8,graph_viz_used=False): 1237 | 1238 | color_class = LIST_COLOR 1239 | 1240 | G = self.graphs[length]['graph'] 1241 | G_nx = nx.DiGraph(G['list_edge']) 1242 | if pos is None: 1243 | if graph_viz_used: 1244 | pos = nx.nx_agraph.graphviz_layout(G_nx,prog="fdp") 1245 | else: 1246 | pos = nx.random_layout(G_nx) 1247 | 1248 | 1249 | G_label_0,dict_node_0,edge_size_0 = self.__format_graph_viz(G_nx,G['list_edge'],G['dict_node']) 1250 | 1251 | all_graphoid,names_features = self.compute_graphoids(length,mode,majority_level) 1252 | all_graphoid = np.array(all_graphoid) 1253 | color_map = [] 1254 | node_width = [] 1255 | labels_text = {} 1256 | for node in G_label_0: 1257 | if graphoid == 'all': 1258 | color_n = self.__combine_hex_values( 1259 | {hc[1:]:val for hc,val in zip( 1260 | color_class[:len(all_graphoid)], 1261 | all_graphoid[:,names_features.index(node)])}) 1262 | color_n = self.__combine_hex_values({color_n:0.95,'a2a2a2': 0.1}) 1263 | node_width_val = 1 1264 | labels_text[node] = node 1265 | else: 1266 | color_n = self.__combine_hex_values( 1267 | {'ff0000': all_graphoid[:,names_features.index(node)][graphoid], 1268 | 'f8f8f8': 0.1}) 1269 | 1270 | if all_graphoid[:,names_features.index(node)][graphoid] > 0: 1271 | node_width_val = 1 1272 | labels_text[node] = node 1273 | else: 1274 | node_width_val = 0.2 1275 | 1276 | node_width.append(node_width_val) 1277 | color_map.append('#' + color_n) 1278 | 1279 | color_map_edge = [] 1280 | for i,(u,v) in enumerate(G_label_0.edges()): 1281 | if graphoid == 'all': 1282 | color_n = self.__combine_hex_values( 1283 | {hc[1:]:val for hc,val in zip( 1284 | color_class[:len(all_graphoid)], 1285 | all_graphoid[:,names_features.index("['{}', '{}']".format(u,v))])}) 1286 | color_n = self.__combine_hex_values({color_n:0.90,'a2a2a2': 0.1}) 1287 | edge_size_0[i] = edge_size_0[i]*2 1288 | else: 1289 | color_n = self.__combine_hex_values( 1290 | {'ff0000': all_graphoid[:,names_features.index("['{}', '{}']".format(u,v))][graphoid], 1291 | 'a2a2a2': 0.1}) 1292 | if all_graphoid[:,names_features.index("['{}', '{}']".format(u,v))][graphoid] > 0: 1293 | edge_size_0[i] = edge_size_0[i] + 5 1294 | else: 1295 | edge_size_0[i] = 0.5 1296 | 1297 | color_map_edge.append('#' + color_n) 1298 | 1299 | 1300 | 1301 | nx.draw( 1302 | G_label_0, 1303 | pos=pos, 1304 | node_color=color_map, 1305 | edge_color=color_map_edge, 1306 | node_size=dict_node_0, 1307 | linewidths=node_width, 1308 | width=edge_size_0) 1309 | nx.draw_networkx_labels(G_label_0, pos, labels_text,font_size=10) 1310 | ax = plt.gca() 1311 | ax.collections[0].set_edgecolor("black") 1312 | 1313 | 1314 | def __verboseprint(self,*args): 1315 | if self.verbose: 1316 | for arg in args: 1317 | print(arg,end=' ') 1318 | print() 1319 | else: 1320 | verboseprint = lambda *a: None 1321 | 1322 | def __combine_hex_values(self,d): 1323 | d_items = sorted(d.items()) 1324 | tot_weight = sum(d.values()) 1325 | zpad = lambda x: x if len(x)==2 else '0' + x 1326 | if tot_weight == 0: 1327 | return zpad(hex(255)[2:]) + zpad(hex(255)[2:]) + zpad(hex(255)[2:]) 1328 | red = int(sum([int(k[:2], 16)*v for k, v in d_items])/tot_weight) 1329 | green = int(sum([int(k[2:4], 16)*v for k, v in d_items])/tot_weight) 1330 | blue = int(sum([int(k[4:6], 16)*v for k, v in d_items])/tot_weight) 1331 | 1332 | return zpad(hex(red)[2:]) + zpad(hex(green)[2:]) + zpad(hex(blue)[2:]) 1333 | 1334 | -------------------------------------------------------------------------------- /kgraph/utils.py: -------------------------------------------------------------------------------- 1 | LIST_COLOR = [ 2 | "#ff8c00","#0000ff","#808000","#ff0000","#00ffff", 3 | "#f0ffff","#f5f5dc","#000000","#a52a2a","#00ffff", 4 | "#00008b","#008b8b","#a9a9a9","#006400","#bdb76b", 5 | "#8b008b","#556b2f","#9932cc","#8b0000","#e9967a", 6 | "#9400d3","#ff00ff","#ffd700","#008000","#4b0082", 7 | "#f0e68c","#add8e6","#e0ffff","#90ee90","#d3d3d3", 8 | "#ffb6c1","#ffffe0","#00ff00","#ff00ff","#800000", 9 | "#000080","#808000","#ffa500","#ffc0cb","#800080", 10 | "#800080","#c0c0c0","#ffffff","#ffff00"] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aeon==0.6.0 2 | matplotlib==3.7.2 3 | networkx==3.2.1 4 | numpy==1.24.3 5 | pandas==2.0.3 6 | scikit_learn==1.3.0 7 | scipy==1.11.4 8 | setuptools>=65.5.1 9 | -------------------------------------------------------------------------------- /ressources/Trace_cluster_interpretation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/ressources/Trace_cluster_interpretation.jpg -------------------------------------------------------------------------------- /ressources/Trace_kgraph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/ressources/Trace_kgraph.jpg -------------------------------------------------------------------------------- /ressources/kGraph_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/ressources/kGraph_logo.png -------------------------------------------------------------------------------- /ressources/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boniolp/kGraph/c7fae5945dce9d1145f4376d5cb6103e8ae24f22/ressources/pipeline.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = LICENSE 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | __version__ = "0.0.1" 4 | 5 | CLASSIFIERS = [ 6 | "Development Status :: 5 - Production/Stable", 7 | "Intended Audience :: Developers", 8 | "License :: OSI Approved :: MIT License", 9 | "Natural Language :: English", 10 | "Operating System :: OS Independent", 11 | "Programming Language :: Python", 12 | "Programming Language :: Python :: 3.9", 13 | "Topic :: Scientific/Engineering :: Information Analysis", 14 | "Topic :: Scientific/Engineering :: Mathematics", 15 | "Topic :: Software Development :: Libraries :: Python Modules", 16 | ] 17 | 18 | setup( 19 | name="kgraph-ts", 20 | version=__version__, 21 | description="kGraph", 22 | classifiers=CLASSIFIERS, 23 | author="Paul Boniol", 24 | author_email="paul.boniol@inria.fr", 25 | packages=find_packages(), 26 | zip_safe=True, 27 | license="", 28 | url="https://github.com/boniolp/kGraph", 29 | entry_points={}, 30 | install_requires=[ 31 | 'aeon==0.6.0', 32 | 'setuptools>=65.5.1', 33 | 'numpy==1.24.4', 34 | 'pandas==2.0.3', 35 | 'matplotlib==3.7.2', 36 | 'scipy==1.11.3', 37 | 'scikit-learn==1.2.2', 38 | 'networkx==3.1', 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | 4 | 5 | 6 | def fetch_ucr_dataset(dataset,path,variable_length=False): 7 | 8 | if variable_length: 9 | path += '{}/'.format(dataset) 10 | with open(path + "{}_TRAIN.tsv".format(dataset),'r') as f: 11 | train = f.readlines() 12 | train = [train_line.replace('\n','') for train_line in train] 13 | labels_train = [] 14 | ts_train = [] 15 | for train_line in train: 16 | val = train_line.split('\t') 17 | labels_train.append(int(val[0])) 18 | ts_train.append(np.array([float(v) for v in val[1:]])) 19 | ts_train[-1] = ts_train[-1][~np.isnan(ts_train[-1])] 20 | 21 | with open(path + "{}_TEST.tsv".format(dataset),'r') as f: 22 | test = f.readlines() 23 | test = [test_line.replace('\n','') for test_line in test] 24 | labels_test = [] 25 | ts_test = [] 26 | for test_line in test: 27 | val = test_line.split('\t') 28 | labels_test.append(int(val[0])) 29 | ts_test.append(np.array([float(v) for v in val[1:]])) 30 | ts_test[-1] = ts_test[-1][~np.isnan(ts_test[-1])] 31 | return {'data_train':ts_train,'target_train':np.array(labels_train), 'data_test':ts_test, 'target_test':np.array(labels_test)} 32 | 33 | else: 34 | path += '{}/'.format(dataset) 35 | train_data = pd.read_csv(path + "{}_TRAIN.tsv".format(dataset),sep='\t',header=None) 36 | target_train = np.array(train_data[0].values) 37 | train_data = train_data.drop(0,axis=1) 38 | train_data = train_data.fillna(0) 39 | data_train = np.array(train_data.values) 40 | data_train = (data_train - np.mean(data_train,axis=1,keepdims=True))/(np.std(data_train,axis=1,keepdims=True)) 41 | 42 | test_data = pd.read_csv(path + "{}_TEST.tsv".format(dataset),sep='\t',header=None) 43 | target_test = np.array(test_data[0].values) 44 | test_data = test_data.drop(0,axis=1) 45 | test_data = test_data.fillna(0) 46 | data_test = np.array(test_data.values) 47 | data_test = (data_test - np.mean(data_test,axis=1,keepdims=True))/(np.std(data_test,axis=1,keepdims=True)) 48 | return {'data_train':data_train,'target_train':target_train, 'data_test':data_test, 'target_test':target_test} 49 | 50 | 51 | def fetch_ucr_dataset_online(dataset): 52 | from aeon.datasets import load_classification 53 | dataCof = load_classification("Trace") 54 | X = np.squeeze(dataCof[0], axis=1) 55 | y = dataCof[1].astype(int) 56 | return X, y --------------------------------------------------------------------------------