├── .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 |
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
--------------------------------------------------------------------------------