correlation-network 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,385 @@
1
+ Metadata-Version: 2.5
2
+ Name: correlation-network
3
+ Version: 1.0.0
4
+ Summary: Build and analyze association networks from pandas DataFrames: correlations, Cramer's V, mutual information and PhiK, with centrality analysis and visualization
5
+ Project-URL: Homepage, https://github.com/skateddu/correlation-network
6
+ Project-URL: Repository, https://github.com/skateddu/correlation-network
7
+ Project-URL: Issues, https://github.com/skateddu/correlation-network/issues
8
+ Project-URL: Changelog, https://github.com/skateddu/correlation-network/blob/main/CHANGELOG.md
9
+ Author-email: Andrea Cadeddu <cadeddu7@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: association,centrality,correlation,cramers-v,graph,mutual-information,network,networkx,pandas
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
22
+ Classifier: Topic :: Scientific/Engineering :: Visualization
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: matplotlib>=3.7
26
+ Requires-Dist: networkx>=3.0
27
+ Requires-Dist: numpy>=1.24
28
+ Requires-Dist: pandas>=2.0
29
+ Requires-Dist: scipy>=1.11
30
+ Provides-Extra: phik
31
+ Requires-Dist: phik>=0.12.2; extra == 'phik'
32
+ Provides-Extra: polars
33
+ Requires-Dist: polars>=0.20; extra == 'polars'
34
+ Requires-Dist: pyarrow>=14.0; extra == 'polars'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Correlation Network
38
+
39
+ [![PyPI](https://img.shields.io/pypi/v/correlation-network)](https://pypi.org/project/correlation-network/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/correlation-network?logo=python&logoColor=white)](https://pypi.org/project/correlation-network/)
41
+ [![CI](https://github.com/skateddu/correlation-network/actions/workflows/ci.yml/badge.svg)](https://github.com/skateddu/correlation-network/actions/workflows/ci.yml)
42
+ ![License](https://img.shields.io/badge/license-MIT-green)
43
+ ![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-d4aa00)
44
+ ![Type: Typed](https://img.shields.io/badge/typing-typed-blue)
45
+
46
+ Python library that builds association networks from pandas DataFrames. Computes pairwise association measures (correlations, Cramer's V, mutual information, etc.), applies threshold filtering, and produces NetworkX graphs with centrality analysis and visualization.
47
+
48
+ ## Features
49
+
50
+ - **Association methods**: Pearson, Spearman, Kendall, Cramer's V, Correlation Ratio, Mutual Information, PhiK
51
+ - **Auto-detection**: selects the right measure based on column types (numeric, categorical, mixed)
52
+ - **API**: scikit-learn-style `fit()` / `transform()` / `fit_transform()` pipeline
53
+ - **Centrality**: degree, strength, betweenness, eigenvector, closeness — individual or summary table
54
+ - **Visualization**: continuous colormap, community detection coloring, centrality-scaled nodes, per-edge alpha
55
+ - **Extensible**: any object implementing the `AssociationStrategy` protocol can be plugged in
56
+
57
+ ## Dependencies
58
+
59
+ | Package | Description | Link |
60
+ |---------|-------------|------|
61
+ | [NetworkX](https://networkx.org/) | Graph data structures, algorithms, and layout | [Docs](https://networkx.org/documentation/stable/) |
62
+ | [pandas](https://pandas.pydata.org/) | DataFrame input and association matrix storage | [Docs](https://pandas.pydata.org/docs/) |
63
+ | [NumPy](https://numpy.org/) | Array operations for matrix computation | [Docs](https://numpy.org/doc/stable/) |
64
+ | [SciPy](https://scipy.org/) | Chi-squared test (Cramer's V), entropy (Mutual Information) | [Docs](https://docs.scipy.org/doc/scipy/) |
65
+ | [Matplotlib](https://matplotlib.org/) | Graph rendering, colormaps, and figure export | [Docs](https://matplotlib.org/stable/contents.html) |
66
+
67
+ Optional extras:
68
+
69
+ | Extra | Description | Link |
70
+ |-------|-------------|------|
71
+ | `phik` | PhiK correlation (works on any column type pair) | [Docs](https://phik.readthedocs.io/) |
72
+ | `polars` | Accept Polars DataFrames as input (auto-converted to pandas) | [Docs](https://docs.pola.rs/) |
73
+
74
+ ## Installation
75
+
76
+ Requires Python >= 3.10.
77
+
78
+ ```bash
79
+ pip install correlation-network
80
+ ```
81
+
82
+ With optional extras:
83
+
84
+ ```bash
85
+ pip install "correlation-network[phik]" # PhiK correlation support
86
+ pip install "correlation-network[polars]" # Polars DataFrame support
87
+ pip install "correlation-network[phik,polars]"
88
+ ```
89
+
90
+ With [uv](https://docs.astral.sh/uv/): `uv add correlation-network` (extras:
91
+ `uv add "correlation-network[phik]"`).
92
+
93
+ ## Quick Start
94
+
95
+ ### Python API
96
+
97
+ ```python
98
+ import pandas as pd
99
+ from correlation_network import CorrelationNetwork, CentralityAnalyzer, NetworkVisualizer
100
+
101
+ # Build the network (default: method="pearson", threshold=0.5)
102
+ net = CorrelationNetwork(method="spearman", threshold=0.3)
103
+ graph = net.fit_transform(df)
104
+
105
+ # Analyze centrality
106
+ analyzer = CentralityAnalyzer(graph)
107
+ rankings = analyzer.summary()
108
+ print(rankings)
109
+
110
+ # Visualize
111
+ viz = NetworkVisualizer(graph)
112
+ fig = viz.plot(
113
+ centrality_metric="degree",
114
+ community_colors=True,
115
+ title="Association Network",
116
+ )
117
+ ```
118
+
119
+ ### Save directly
120
+
121
+ ```python
122
+ viz.save("network.png", dpi=150, community_colors=True, title="My Network")
123
+ ```
124
+
125
+ ### From a pre-computed matrix
126
+
127
+ ```python
128
+ # Use a correlation matrix you already have
129
+ net = CorrelationNetwork.from_matrix(my_matrix, threshold=0.5)
130
+ graph = net.transform()
131
+ ```
132
+
133
+ ### With Polars DataFrames
134
+
135
+ ```python
136
+ import polars as pl
137
+
138
+ df_polars = pl.read_csv("data.csv")
139
+ graph = CorrelationNetwork(method="pearson", threshold=0.5).fit_transform(df_polars)
140
+ ```
141
+
142
+ ## Constructor Parameters
143
+
144
+ | Parameter | Default | Description |
145
+ |-----------|---------|-------------|
146
+ | `method` | `"pearson"` | Association method (see table below) |
147
+ | `threshold` | `0.5` | Minimum `\|weight\|` to keep an edge (inclusive) |
148
+ | `strategy` | `None` | Custom `AssociationStrategy` instance (overrides `method`) |
149
+ | `cardinality_threshold` | `0.95` | Exclude index-like columns with `nunique/nrows >= threshold` (see below) |
150
+
151
+ ## Preprocessing
152
+
153
+ Before computing associations, `fit()` automatically excludes:
154
+
155
+ - **Datetime columns** (not suitable for correlation)
156
+ - **High-cardinality columns** where `nunique / nrows >= cardinality_threshold` (e.g. IDs, UUIDs).
157
+ Integer columns are excluded only when their values also form a consecutive
158
+ sequence (row numbers, auto-increment keys, even if shuffled): on small frames
159
+ genuine integer measurements such as age or income are near-unique too
160
+
161
+ After computing the matrix, columns the strategy cannot use (e.g. strings with
162
+ `method="pearson"`) are reported with a warning instead of silently disappearing
163
+ from the graph.
164
+
165
+ All these columns are stored in `net.excluded_columns_` after fitting:
166
+
167
+ ```python
168
+ net = CorrelationNetwork(method="auto", threshold=0.3)
169
+ net.fit(df)
170
+ print(net.excluded_columns_) # ['timestamp', 'row_id']
171
+ ```
172
+
173
+ ## Association Methods
174
+
175
+ | Method | Strategy Class | Column Types | Output Range |
176
+ |--------|---------------|-------------|--------------|
177
+ | `pearson` | `PearsonStrategy` | numeric — numeric | [-1, 1] |
178
+ | `spearman` | `SpearmanStrategy` | numeric — numeric | [-1, 1] |
179
+ | `kendall` | `KendallStrategy` | numeric — numeric | [-1, 1] |
180
+ | `cramers_v` | `CramersVStrategy` | categorical — categorical | [0, 1] |
181
+ | `correlation_ratio` | `CorrelationRatioStrategy` | categorical — numeric | [0, 1] |
182
+ | `mutual_information` | `MutualInformationStrategy` | any | [0, 1] |
183
+ | `phik` | `PhiKStrategy` | any | [0, 1] |
184
+ | `auto` | `AutoAssociationStrategy` | auto-detects types | [0, 1] |
185
+
186
+ Each method requires columns of the type it operates on. Passing a DataFrame
187
+ that cannot satisfy it raises `InsufficientColumnsError` (attributes `method`,
188
+ `required`, `found`) rather than silently returning an empty matrix.
189
+
190
+ ### Missing values
191
+
192
+ Every method evaluates a column pair on the rows where **both** values are
193
+ present (pairwise-complete observations), the same convention `pandas.corr()`
194
+ uses. Rows missing elsewhere in the DataFrame do not affect an unrelated pair.
195
+
196
+ If a pair shares no complete rows at all, its association is undefined and
197
+ `NoCompleteObservationsError` (attribute `columns`) is raised rather than a
198
+ misleading zero.
199
+
200
+ > **Mutual information and bin count.** `MutualInformationStrategy` discretizes
201
+ > numeric columns into `n_bins` (default 10) before computing MI. With few
202
+ > samples per bin the estimate is biased upward: on 200 rows, two *independent*
203
+ > columns score ≈0.10 at `n_bins=10` but ≈0.50 at `n_bins=50`. Raise `n_bins`
204
+ > only when you have the rows to support it, and set `threshold` accordingly.
205
+ >
206
+ > Bins are equal-width by default, so a single extreme outlier can squeeze
207
+ > almost every row into one bin and hide a real relationship. Pass
208
+ > `binning="quantile"` for equal-frequency bins on skewed or outlier-prone data:
209
+ >
210
+ > ```python
211
+ > from correlation_network import MutualInformationStrategy
212
+ >
213
+ > net = CorrelationNetwork(strategy=MutualInformationStrategy(binning="quantile"))
214
+ > ```
215
+
216
+ ### Error handling
217
+
218
+ All library errors derive from `CorrelationNetworkError`, so a single `except`
219
+ catches any of them. Each one also keeps its built-in base (`ValueError`, and
220
+ `AttributeError` for `NotFittedError`), so existing handlers keep working.
221
+
222
+ ### Auto mode
223
+
224
+ When `method="auto"`, the library detects column dtypes and dispatches:
225
+
226
+ | Pair type | Default strategy |
227
+ |-----------|-----------------|
228
+ | numeric — numeric | Spearman |
229
+ | categorical — categorical | Cramer's V |
230
+ | mixed | Correlation Ratio |
231
+
232
+ Bool and string columns count as categorical.
233
+
234
+ ```python
235
+ net = CorrelationNetwork(method="auto", threshold=0.3)
236
+ graph = net.fit_transform(df)
237
+ ```
238
+
239
+ > **Auto mode discards the sign.** Numeric correlations are reduced to their
240
+ > absolute value so all three measures share a [0, 1] scale (Cramer's V and eta
241
+ > are non-negative by definition). A Spearman of `-0.98` becomes `0.98`, and the
242
+ > positive/negative edge colouring in `NetworkVisualizer` has no effect on such a
243
+ > graph. Use an explicit correlation method when the direction matters.
244
+
245
+ ### Custom strategies
246
+
247
+ Any object implementing the `AssociationStrategy` protocol (`compute(df) -> pd.DataFrame`) can be used:
248
+
249
+ ```python
250
+ net = CorrelationNetwork(strategy=my_custom_strategy, threshold=0.3)
251
+ ```
252
+
253
+ ## Centrality Analysis
254
+
255
+ | Method | Description |
256
+ |--------|-------------|
257
+ | `degree()` | Normalized degree: connected neighbors as fraction of (n-1) |
258
+ | `strength()` | Sum of absolute edge weights |
259
+ | `betweenness()` | Frequency on shortest paths (weights converted to distances) |
260
+ | `eigenvector()` | Influence based on connections to high-scoring nodes (absolute weights) |
261
+ | `closeness()` | Inverse average distance to all other nodes (weights converted to distances) |
262
+ | `summary()` | DataFrame with all metrics combined |
263
+
264
+ ```python
265
+ analyzer = CentralityAnalyzer(graph)
266
+
267
+ analyzer.degree() # pd.Series, sorted descending
268
+ analyzer.summary() # pd.DataFrame with all 5 metrics
269
+ ```
270
+
271
+ ### Association strength vs. graph distance
272
+
273
+ `betweenness()` and `closeness()` are path-based, and NetworkX reads the edge
274
+ attribute it is given as a **cost**. Association values run the other way — a
275
+ weight near 1 means two nodes are *close*, not far apart. Both metrics therefore
276
+ convert weights with `distance = 1 - |weight|` before running Dijkstra, so
277
+ shortest paths follow strongly associated pairs.
278
+
279
+ `strength()` is a magnitude sum rather than a path metric, so it keeps using the
280
+ raw absolute weights. `eigenvector()` also runs on absolute weights: signed
281
+ weights can yield negative scores, which have no meaning as centrality.
282
+
283
+ The default `kamada_kawai` layout applies the same `1 - |weight|` conversion,
284
+ since it reads weights as target distances: strongly associated nodes are drawn
285
+ close together.
286
+
287
+ ## Visualization
288
+
289
+ | Parameter | Default | Description |
290
+ |-----------|---------|-------------|
291
+ | `layout` | `kamada_kawai` | Layout algorithm (`spring`, `circular`, `shell`, `spectral`) |
292
+ | `centrality_metric` | `None` | Scale node size by centrality (`degree`, `strength`, etc.) |
293
+ | `figsize` | `(10, 8)` | Figure size in inches |
294
+ | `dpi` | `100` | Figure resolution |
295
+ | `node_color` | `tab:blue` | Node color (ignored when `community_colors=True`) |
296
+ | `community_colors` | `False` | Color nodes by greedy modularity community |
297
+ | `edge_cmap` | `RdYlGn` | Diverging colormap for edges, centred at 0 (`None` for two-color mode) |
298
+ | `positive_edge_color` | `darkgreen` | Positive edge color (two-color mode only) |
299
+ | `negative_edge_color` | `tab:red` | Negative edge color (two-color mode only) |
300
+ | `font_size` | `10` | Font size for node labels |
301
+ | `edge_label_font_size` | `8` | Font size for edge weight labels |
302
+ | `show_edge_labels` | `True` | Display edge weight labels |
303
+ | `title` | `None` | Plot title |
304
+
305
+ ```python
306
+ viz = NetworkVisualizer(graph)
307
+
308
+ fig = viz.plot(
309
+ layout="kamada_kawai",
310
+ centrality_metric="degree",
311
+ community_colors=True,
312
+ edge_cmap="RdYlGn",
313
+ title="My Network",
314
+ )
315
+
316
+ viz.save("network.png", dpi=150, community_colors=True)
317
+ ```
318
+
319
+ ## Development
320
+
321
+ Setup, test commands and contribution guidelines are in [CONTRIBUTING.md](https://github.com/skateddu/correlation-network/blob/main/CONTRIBUTING.md).
322
+
323
+ ### Architecture
324
+
325
+ The library follows a pipeline pattern: DataFrame in, graph out.
326
+
327
+ ```mermaid
328
+ graph LR
329
+ A[DataFrame] -->|fit| B[Association Matrix]
330
+ B -->|transform| C[nx.Graph]
331
+ C --> D[CentralityAnalyzer]
332
+ C --> E[NetworkVisualizer]
333
+ ```
334
+
335
+ | Concept | Implementation | Module |
336
+ |---------|---------------|--------|
337
+ | Pluggable metrics | `AssociationStrategy` Protocol | `association/_base.py` |
338
+ | Metric dispatch | `_STRATEGY_REGISTRY` dict | `network.py` |
339
+ | Fit/transform pipeline | scikit-learn-style API | `network.py` |
340
+ | Graph centrality | NetworkX algorithms wrapper | `centrality.py` |
341
+ | Negative weight handling | `abs_weight_graph()` helper | `_graph_utils.py` |
342
+ | Association → distance | `distance_graph()` helper | `_graph_utils.py` |
343
+ | Fitted state guard | `check_is_fitted()` function | `_validation.py` |
344
+ | Rendering | Matplotlib + NetworkX drawing | `visualization.py` |
345
+
346
+ ### Project Structure
347
+
348
+ ```
349
+ correlation-network/
350
+ ├── src/correlation_network/
351
+ │ ├── __init__.py # Public API exports
352
+ │ ├── network.py # CorrelationNetwork (main entry point)
353
+ │ ├── centrality.py # CentralityAnalyzer (5 metrics + summary)
354
+ │ ├── visualization.py # NetworkVisualizer (5 layouts, colormap, community colors)
355
+ │ ├── exceptions.py # CorrelationNetworkError base + NotFittedError, InsufficientColumnsError, NoCompleteObservationsError
356
+ │ ├── py.typed # PEP 561 marker: ships inline type information
357
+ │ ├── _types.py # Literal type aliases (AssociationMethod, BinningMethod, CentralityMetric, LayoutAlgorithm, CorrelationMethod)
358
+ │ ├── _validation.py # Fitted guard, missing-data checks, option validation
359
+ │ ├── _graph_utils.py # Shared graph utilities (abs_weight_graph, distance_graph)
360
+ │ └── association/
361
+ │ ├── __init__.py # Re-exports all strategies
362
+ │ ├── _base.py # AssociationStrategy (Protocol)
363
+ │ ├── _dtypes.py # Numeric vs categorical column detection
364
+ │ ├── _correlation.py # Pearson, Spearman, Kendall
365
+ │ ├── _categorical.py # Cramer's V, Correlation Ratio (eta)
366
+ │ ├── _universal.py # PhiK, Mutual Information
367
+ │ └── _auto.py # Auto-detection + dispatch
368
+ ├── tests/
369
+ │ ├── conftest.py # Shared fixtures (numeric, categorical, mixed DataFrames)
370
+ │ ├── unit/ # One test file per module
371
+ │ └── integration/ # End-to-end pipeline tests
372
+ ├── .github/workflows/ci.yml # Lint, format, mypy, test matrix (3.10–3.13), lowest dependency versions
373
+ ├── pyproject.toml # Metadata, dependencies, ruff, mypy and pytest config
374
+ ├── uv.lock # Locked dependency versions
375
+ ├── CHANGELOG.md # Keep a Changelog format
376
+ ├── CONTRIBUTING.md # Development setup and workflow
377
+ └── LICENSE # MIT
378
+ ```
379
+
380
+ ## References
381
+
382
+ - Newman, M. E. J. (2010). *Networks: An Introduction*. Oxford University Press.
383
+ - Cramér, H. (1946). *Mathematical Methods of Statistics*. Princeton University Press.
384
+ - Bergsma, W. (2018). [A new correlation coefficient](https://arxiv.org/abs/1712.05289). *arXiv:1712.05289* — PhiK foundation.
385
+ - Reshef, D. et al. (2011). [Detecting Novel Associations in Large Data Sets](https://doi.org/10.1126/science.1205438). *Science*.
@@ -0,0 +1,20 @@
1
+ correlation_network/__init__.py,sha256=A4iuT41a3Weq9U2p4OQFBUqTqTyOrbyZw2Rh-K4IOfg,1528
2
+ correlation_network/_graph_utils.py,sha256=cvvBMJi5SuNdvTIHokaQvyX1Z_upY2_982zX747KYco,1965
3
+ correlation_network/_types.py,sha256=3Tbyv5F6hu-uJV3GJWRBf-4XKQemrqgY9X9E8RlzC8Y,572
4
+ correlation_network/_validation.py,sha256=hUKez1UtD9xU5ogvOPzHtWiTGBTuzMp9XAIjuuNWjUM,3462
5
+ correlation_network/centrality.py,sha256=byNPoS22mh1XoRcboG0cR1rxE2Yze0VYwBgGp-UntHY,4036
6
+ correlation_network/exceptions.py,sha256=QhYhR_Ra-PUuGK6eLPsIb6IfRroVQ6WXIXPBOQEQ3C8,2117
7
+ correlation_network/network.py,sha256=hKyZjmaanzwErEuNVTSs7-lr1MK-PkanPhKJrv_Kprw,14835
8
+ correlation_network/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ correlation_network/visualization.py,sha256=CANiCTOgEsla0utzD_fQt1fEem0ODSmDlgpoclEmBPE,13565
10
+ correlation_network/association/__init__.py,sha256=psNUEM3zzkrTlT2UkwSoTX1SflH_2uia-nbVJGA8q6o,810
11
+ correlation_network/association/_auto.py,sha256=q4cOnuJy_Q-BnaLgNKkvm-5BsJ_mqggXPy5icB6lwr8,4239
12
+ correlation_network/association/_base.py,sha256=hfRpn-oLAfDdjHAF0a8a6uMuXxLUldXV2MHzDSSLaio,642
13
+ correlation_network/association/_categorical.py,sha256=rrTuzYaUqoE217IqX6kENKlnD2AWQy0E6_2ZbcDDIEk,6379
14
+ correlation_network/association/_correlation.py,sha256=KAVbmLk94bR1eG2W3lCGVvcXHYjMs7aUyGhLrK5mhDQ,2032
15
+ correlation_network/association/_dtypes.py,sha256=z9MMYdEY50WiV_Xp-D9c1qdk3aQcNxwSzk7epXzGYE4,1219
16
+ correlation_network/association/_universal.py,sha256=wDh9ENdbcluHxzo3W_Fe3Nrg5yOcv3vWpw7VLnzjgO8,5379
17
+ correlation_network-1.0.0.dist-info/METADATA,sha256=4ha0V4qV9i3E7oxu53Z2JSsOE77f6us8JNgsBYDGurs,16881
18
+ correlation_network-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
19
+ correlation_network-1.0.0.dist-info/licenses/LICENSE,sha256=dT_fixOtJZW_T2IpUZSTF4YLnY8DpDhgoHHOsRRDTxU,1071
20
+ correlation_network-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrea Cadeddu
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.