Skip to content

Commit

Permalink
Merge pull request #52 from zStupan/main
Browse files Browse the repository at this point in the history
Add visualization test
  • Loading branch information
firefly-cpp authored Apr 20, 2022
2 parents 7a3b717 + 207cf21 commit d8e5134
Show file tree
Hide file tree
Showing 12 changed files with 781 additions and 3 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
Binary file added .github/images/hill_slopes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img alt="logo" width="300" src="https://raw.githubusercontent.com/firefly-cpp/NiaARM/main/.github/logo/logo.png">
<img alt="logo" width="300" src="https://raw.githubusercontent.com/firefly-cpp/NiaARM/main/.github/images/logo.png">
</p>

---
Expand Down Expand Up @@ -28,6 +28,7 @@ The current version includes (but is not limited to) the following functions:
- searching for association rules,
- providing output of mined association rules,
- generating statistics about mined association rules.
- visualization of association rules

## Installation

Expand Down Expand Up @@ -135,6 +136,25 @@ problem.rules.sort()
problem.rules.to_csv('output.csv')
```

### Visualization

The framework currently supports the hill slopes visualization method presented in [4].

```python
from matplotlib import pyplot as plt
from niaarm.visualize import hill_slopes

# load data...
# mine rules...

hill_slopes(rule, dataset.transactions)
plt.show()
```
<p>
<img alt="logo" src="https://raw.githubusercontent.com/firefly-cpp/NiaARM/main/.github/images/hill_slopes.png">
</p>


For a full list of examples see the [examples folder](https://github.com/firefly-cpp/NiaARM/tree/main/examples)
in the GitHub repository.

Expand Down Expand Up @@ -190,6 +210,10 @@ Ideas are based on the following research papers:

[3] I. Fister Jr., I. Fister [A brief overview of swarm intelligence-based algorithms for numerical association rule mining](https://arxiv.org/abs/2010.15524). arXiv preprint arXiv:2010.15524 (2020).

[4] Fister, I. et al. (2020). Visualization of Numerical Association Rules by Hill Slopes.
In: Analide, C., Novais, P., Camacho, D., Yin, H. (eds) Intelligent Data Engineering and Automated Learning – IDEAL 2020.
IDEAL 2020. Lecture Notes in Computer Science(), vol 12489. Springer, Cham. https://doi.org/10.1007/978-3-030-62362-3_10

## License

This package is distributed under the MIT License. This license can be found online at <http://www.opensource.org/licenses/MIT>.
Expand Down
2 changes: 2 additions & 0 deletions niaarm/tests/test_data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
- The Abalone dataset is downloaded from https://archive.ics.uci.edu/ml/index.php.

- The wiki_test_case dataset is composed from the first Table found in: https://en.wikipedia.org/wiki/Lift_(data_mining)

- SpotyDataGen - Iztok Fister Jr., Grega Vrbančič, Lucija Brezočnik, Vili Podgorelec, Iztok Fister. [SportyDataGen: an online generator of endurance sports activity collections](http://iztok-jr-fister.eu/static/publications/225.pdf). In: CECIIS: Central European Conference on Information and Intelligent Systems, pp. 171-178, 2018.
701 changes: 701 additions & 0 deletions niaarm/tests/test_data/sportydatagen_generated.csv

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions niaarm/tests/test_visualization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os
from unittest import TestCase
import numpy as np
from niaarm import Dataset, Feature, Rule
from niaarm.visualize import hill_slopes, _ribbon


class TestHillSlopes(TestCase):
def setUp(self):
sporty = Dataset(os.path.join(os.path.dirname(__file__), 'test_data', 'sportydatagen_generated.csv'))
self.transactions = sporty.transactions
antecedent = [
Feature('duration', 'float', 46.9530354402242, 65.87258373112326),
Feature('distance', 'float', 26.23676635110497, 53.29979966985809),
Feature('average_hr', 'float', 104.1241905565174, 141.39599912527686),
Feature('average_altitude', 'float', 17.587384648223903, 547.0467243284303),
]

consequent = [
Feature('calories', 'float', 1096.8185894801436, 1209.0),
Feature('ascent', 'float', 0.0, 74.19297690681586),
Feature('descent', 'float', 0.0, 623.8817163897467),
]

self.rule = Rule(antecedent, consequent)

def test_hill_slopes(self):
support = np.array([0.934286, 0.847143, 0.74, 0.561429, 0.244286, 0.225714, 0.00714286])
confidence = np.array([0.934286, 0.847095, 0.753823, 0.570336, 0.261468, 0.224771, 0.00458716])
length = np.array([1.32128, 1.19801, 1.05634, 0.800303, 0.357828, 0.318542, 0.00848896])
position = np.array([0.66064, 2.76738, 4.64837, 6.14703, 6.98756, 7.55052, 7.71862])

s = (length + support + confidence) / 2
a = np.sqrt(s * (s - length) * (s - support) * (s - confidence))
height = 2 * a / length
x = np.sqrt(support ** 2 - height ** 2)

vec = np.concatenate((-length / 2, -length / 2 + x, length / 2))
vec = (vec.reshape(3, 7) + position).T.reshape(len(vec))

height = np.concatenate((height, np.zeros(len(vec) - 7)))
height = np.reshape(height, (3, 7)).T.reshape(len(vec))
height = np.concatenate((np.zeros(1), height))[:len(vec)]

_, ax1 = hill_slopes(self.rule, self.transactions)
_, ax2 = _ribbon(vec, height)
ax1_xx, ax1_yy, ax1_zz, _ = ax1.collections[0]._vec
ax2_xx, ax2_yy, ax2_zz, _ = ax2.collections[0]._vec
self.assertTrue(np.allclose(ax1_xx, ax2_xx))
self.assertTrue(np.allclose(ax1_yy, ax2_yy))
self.assertTrue(np.allclose(ax1_zz, ax2_zz))
4 changes: 2 additions & 2 deletions niaarm/visualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def hill_slopes(rule, transactions):
height = np.reshape(height, (3, num_features)).T.reshape(len(vec))
height = np.concatenate((np.zeros(1), height))[:len(vec)]

fig, ax = ribbon(vec, height)
fig, ax = _ribbon(vec, height)
ax.set_ylabel('Location')
ax.set_yticks(range(num_features + 1))
ax.set_yticklabels(range(num_features + 1))
Expand All @@ -88,7 +88,7 @@ def hill_slopes(rule, transactions):
return fig, ax


def ribbon(x, z, width=0.5):
def _ribbon(x, z, width=0.5):
fig, ax = plt.subplots(subplot_kw={'projection': '3d'})

xi = np.linspace(x[:-1], x[1:], num=100, axis=1).flatten()
Expand Down

0 comments on commit d8e5134

Please sign in to comment.