Skip to content

Commit 9480f9a

Browse files
authored
Merge pull request #29 from KrishnaswamyLab/dev
graphtools v0.2.0
2 parents edc91ea + 76ba4f0 commit 9480f9a

File tree

17 files changed

+670
-195
lines changed

17 files changed

+670
-195
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
sudo: required
88

9+
cache: pip
10+
911
addons:
1012
apt:
1113
packages:

README.rst

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ graphtools
55
.. image:: https://img.shields.io/pypi/v/graphtools.svg
66
:target: https://pypi.org/project/graphtools/
77
:alt: Latest PyPi version
8+
.. image:: https://anaconda.org/conda-forge/tasklogger/badges/version.svg
9+
:target: https://anaconda.org/conda-forge/tasklogger/
10+
:alt: Latest Conda version
811
.. image:: https://api.travis-ci.com/KrishnaswamyLab/graphtools.svg?branch=master
912
:target: https://travis-ci.com/KrishnaswamyLab/graphtools
1013
:alt: Travis CI Build
@@ -28,7 +31,11 @@ Installation
2831

2932
graphtools is available on `pip`. Install by running the following in a terminal::
3033

31-
pip install --user graphtools
34+
pip install --user graphtools
35+
36+
Alternatively, graphtools can be installed using `Conda <https://conda.io/docs/>`_ (most easily obtained via the `Miniconda Python distribution <https://conda.io/miniconda.html>`_)::
37+
38+
conda install -c conda-forge graphtools
3239

3340
Or, to install the latest version from github::
3441

@@ -45,14 +52,14 @@ The `graphtools.Graph` class provides an all-in-one interface for k-nearest neig
4552

4653
Use it as follows::
4754

48-
from sklearn import datasets
49-
import graphtools
50-
digits = datasets.load_digits()
51-
G = graphtools.Graph(digits['data'])
52-
K = G.kernel
53-
P = G.diff_op
54-
G = graphtools.Graph(digits['data'], n_landmark=300)
55-
L = G.landmark_op
55+
from sklearn import datasets
56+
import graphtools
57+
digits = datasets.load_digits()
58+
G = graphtools.Graph(digits['data'])
59+
K = G.kernel
60+
P = G.diff_op
61+
G = graphtools.Graph(digits['data'], n_landmark=300)
62+
L = G.landmark_op
5663

5764
Help
5865
----

graphtools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from .api import Graph
1+
from .api import Graph, from_igraph
22
from .version import __version__

graphtools/api.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import numpy as np
22
import warnings
33
import tasklogger
4+
from scipy import sparse
45

56
from . import base
67
from . import graphs
@@ -9,14 +10,15 @@
910
def Graph(data,
1011
n_pca=None,
1112
sample_idx=None,
12-
adaptive_k='sqrt',
13+
adaptive_k=None,
1314
precomputed=None,
1415
knn=5,
1516
decay=10,
17+
bandwidth=None,
1618
distance='euclidean',
1719
thresh=1e-4,
1820
kernel_symm='+',
19-
gamma=None,
21+
theta=None,
2022
n_landmark=None,
2123
n_svd=100,
2224
beta=1,
@@ -61,6 +63,11 @@ def Graph(data,
6163
decay : `int` or `None`, optional (default: 10)
6264
Rate of alpha decay to use. If `None`, alpha decay is not used.
6365
66+
bandwidth : `float`, list-like or `None`, optional (default: `None`)
67+
Fixed bandwidth to use. If given, overrides `knn`. Can be a single
68+
bandwidth or a list-like (shape=[n_samples]) of bandwidths for each
69+
sample.
70+
6471
distance : `str`, optional (default: `'euclidean'`)
6572
Any metric from `scipy.spatial.distance` can be used
6673
distance metric for building kNN graph.
@@ -75,25 +82,25 @@ def Graph(data,
7582
Defines method of MNN symmetrization.
7683
'+' : additive
7784
'*' : multiplicative
78-
'gamma' : min-max
85+
'theta' : min-max
7986
'none' : no symmetrization
8087
81-
gamma: float (default: None)
82-
Min-max symmetrization constant or matrix. Only used if kernel_symm='gamma'.
83-
K = `gamma * min(K, K.T) + (1 - gamma) * max(K, K.T)`
88+
theta: float (default: None)
89+
Min-max symmetrization constant or matrix. Only used if kernel_symm='theta'.
90+
K = `theta * min(K, K.T) + (1 - theta) * max(K, K.T)`
8491
8592
precomputed : {'distance', 'affinity', 'adjacency', `None`}, optional (default: `None`)
8693
If the graph is precomputed, this variable denotes which graph
8794
matrix is provided as `data`.
8895
Only one of `precomputed` and `n_pca` can be set.
8996
9097
beta: float, optional(default: 1)
91-
Multiply within - batch connections by(1 - beta)
98+
Multiply between - batch connections by beta
9299
93100
sample_idx: array-like
94101
Batch index for MNN kernel
95102
96-
adaptive_k : `{'min', 'mean', 'sqrt', 'none'}` (default: 'sqrt')
103+
adaptive_k : `{'min', 'mean', 'sqrt', 'none'}` (default: None)
97104
Weights MNN kernel adaptively using the number of cells in
98105
each sample according to the selected method.
99106
@@ -221,3 +228,31 @@ def Graph(data,
221228
for key, value in params.items()
222229
if key != "data"])))
223230
return Graph(**params)
231+
232+
233+
def from_igraph(G, **kwargs):
234+
"""Convert an igraph.Graph to a graphtools.Graph
235+
236+
Creates a graphtools.graphs.TraditionalGraph with a
237+
precomputed adjacency matrix
238+
239+
Parameters
240+
----------
241+
G : igraph.Graph
242+
Graph to be converted
243+
kwargs
244+
keyword arguments for graphtools.Graph
245+
246+
Returns
247+
-------
248+
G : graphtools.graphs.TraditionalGraph
249+
"""
250+
if 'precomputed' in kwargs:
251+
if kwargs['precomputed'] != 'adjacency':
252+
warnings.warn(
253+
"Cannot build graph from igraph with precomputed={}. "
254+
"Use 'adjacency' instead.".format(kwargs['precomputed']),
255+
UserWarning)
256+
del kwargs['precomputed']
257+
return Graph(sparse.coo_matrix(G.get_adjacency().data),
258+
precomputed='adjacency', **kwargs)

graphtools/base.py

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,12 @@ class BaseGraph(with_metaclass(abc.ABCMeta, Base)):
311311
Defines method of MNN symmetrization.
312312
'+' : additive
313313
'*' : multiplicative
314-
'gamma' : min-max
314+
'theta' : min-max
315315
'none' : no symmetrization
316316
317-
gamma: float (default: 0.5)
317+
theta: float (default: 0.5)
318318
Min-max symmetrization constant.
319-
K = `gamma * min(K, K.T) + (1 - gamma) * max(K, K.T)`
319+
K = `theta * min(K, K.T) + (1 - theta) * max(K, K.T)`
320320
321321
initialize : `bool`, optional (default : `True`)
322322
if false, don't create the kernel matrix.
@@ -337,11 +337,20 @@ class BaseGraph(with_metaclass(abc.ABCMeta, Base)):
337337
"""
338338

339339
def __init__(self, kernel_symm='+',
340+
theta=None,
340341
gamma=None,
341342
initialize=True, **kwargs):
343+
if gamma is not None:
344+
warnings.warn("gamma is deprecated. "
345+
"Setting theta={}".format(gamma), FutureWarning)
346+
theta = gamma
347+
if kernel_symm == 'gamma':
348+
warnings.warn("kernel_symm='gamma' is deprecated. "
349+
"Setting kernel_symm='theta'", FutureWarning)
350+
kernel_symm = 'theta'
342351
self.kernel_symm = kernel_symm
343-
self.gamma = gamma
344-
self._check_symmetrization(kernel_symm, gamma)
352+
self.theta = theta
353+
self._check_symmetrization(kernel_symm, theta)
345354

346355
if initialize:
347356
tasklogger.log_debug("Initializing kernel...")
@@ -350,25 +359,25 @@ def __init__(self, kernel_symm='+',
350359
tasklogger.log_debug("Not initializing kernel.")
351360
super().__init__(**kwargs)
352361

353-
def _check_symmetrization(self, kernel_symm, gamma):
354-
if kernel_symm not in ['+', '*', 'gamma', None]:
362+
def _check_symmetrization(self, kernel_symm, theta):
363+
if kernel_symm not in ['+', '*', 'theta', None]:
355364
raise ValueError(
356365
"kernel_symm '{}' not recognized. Choose from "
357-
"'+', '*', 'gamma', or 'none'.".format(kernel_symm))
358-
elif kernel_symm != 'gamma' and gamma is not None:
359-
warnings.warn("kernel_symm='{}' but gamma is not None. "
360-
"Setting kernel_symm='gamma'.".format(kernel_symm))
361-
self.kernel_symm = kernel_symm = 'gamma'
362-
363-
if kernel_symm == 'gamma':
364-
if gamma is None:
365-
warnings.warn("kernel_symm='gamma' but gamma not given. "
366-
"Defaulting to gamma=0.5.")
367-
self.gamma = gamma = 0.5
368-
elif not isinstance(gamma, numbers.Number) or \
369-
gamma < 0 or gamma > 1:
370-
raise ValueError("gamma {} not recognized. Expected "
371-
"a float between 0 and 1".format(gamma))
366+
"'+', '*', 'theta', or 'none'.".format(kernel_symm))
367+
elif kernel_symm != 'theta' and theta is not None:
368+
warnings.warn("kernel_symm='{}' but theta is not None. "
369+
"Setting kernel_symm='theta'.".format(kernel_symm))
370+
self.kernel_symm = kernel_symm = 'theta'
371+
372+
if kernel_symm == 'theta':
373+
if theta is None:
374+
warnings.warn("kernel_symm='theta' but theta not given. "
375+
"Defaulting to theta=0.5.")
376+
self.theta = theta = 0.5
377+
elif not isinstance(theta, numbers.Number) or \
378+
theta < 0 or theta > 1:
379+
raise ValueError("theta {} not recognized. Expected "
380+
"a float between 0 and 1".format(theta))
372381

373382
def _build_kernel(self):
374383
"""Private method to build kernel matrix
@@ -400,26 +409,26 @@ def symmetrize_kernel(self, K):
400409
elif self.kernel_symm == "*":
401410
tasklogger.log_debug("Using multiplication symmetrization.")
402411
K = K.multiply(K.T)
403-
elif self.kernel_symm == 'gamma':
412+
elif self.kernel_symm == 'theta':
404413
tasklogger.log_debug(
405-
"Using gamma symmetrization (gamma = {}).".format(self.gamma))
406-
K = self.gamma * elementwise_minimum(K, K.T) + \
407-
(1 - self.gamma) * elementwise_maximum(K, K.T)
414+
"Using theta symmetrization (theta = {}).".format(self.theta))
415+
K = self.theta * elementwise_minimum(K, K.T) + \
416+
(1 - self.theta) * elementwise_maximum(K, K.T)
408417
elif self.kernel_symm is None:
409418
tasklogger.log_debug("Using no symmetrization.")
410419
pass
411420
else:
412421
# this should never happen
413422
raise ValueError(
414-
"Expected kernel_symm in ['+', '*', 'gamma' or None]. "
415-
"Got {}".format(self.gamma))
423+
"Expected kernel_symm in ['+', '*', 'theta' or None]. "
424+
"Got {}".format(self.theta))
416425
return K
417426

418427
def get_params(self):
419428
"""Get parameters from this object
420429
"""
421430
return {'kernel_symm': self.kernel_symm,
422-
'gamma': self.gamma}
431+
'theta': self.theta}
423432

424433
def set_params(self, **params):
425434
"""Set parameters on this object
@@ -429,7 +438,7 @@ def set_params(self, **params):
429438
Valid parameters:
430439
Invalid parameters: (these would require modifying the kernel matrix)
431440
- kernel_symm
432-
- gamma
441+
- theta
433442
434443
Parameters
435444
----------
@@ -439,8 +448,8 @@ def set_params(self, **params):
439448
-------
440449
self
441450
"""
442-
if 'gamma' in params and params['gamma'] != self.gamma:
443-
raise ValueError("Cannot update gamma. Please create a new graph")
451+
if 'theta' in params and params['theta'] != self.theta:
452+
raise ValueError("Cannot update theta. Please create a new graph")
444453
if 'kernel_symm' in params and \
445454
params['kernel_symm'] != self.kernel_symm:
446455
raise ValueError(
@@ -535,6 +544,42 @@ def build_kernel(self):
535544
"""
536545
raise NotImplementedError
537546

547+
def to_pygsp(self, **kwargs):
548+
"""Convert to a PyGSP graph
549+
550+
For use only when the user means to create the graph using
551+
the flag `use_pygsp=True`, and doesn't wish to recompute the kernel.
552+
Creates a graphtools.graphs.TraditionalGraph with a precomputed
553+
affinity matrix which also inherits from pygsp.graphs.Graph.
554+
555+
Parameters
556+
----------
557+
kwargs
558+
keyword arguments for graphtools.Graph
559+
560+
Returns
561+
-------
562+
G : graphtools.base.PyGSPGraph, graphtools.graphs.TraditionalGraph
563+
"""
564+
from . import api
565+
if 'precomputed' in kwargs:
566+
if kwargs['precomputed'] != 'affinity':
567+
warnings.warn(
568+
"Cannot build PyGSPGraph with precomputed={}. "
569+
"Using 'affinity' instead.".format(kwargs['precomputed']),
570+
UserWarning)
571+
del kwargs['precomputed']
572+
if 'use_pygsp' in kwargs:
573+
if kwargs['use_pygsp'] is not True:
574+
warnings.warn(
575+
"Cannot build PyGSPGraph with use_pygsp={}. "
576+
"Use True instead.".format(kwargs['use_pygsp']),
577+
UserWarning)
578+
del kwargs['use_pygsp']
579+
return api.Graph(self.K,
580+
precomputed="affinity", use_pygsp=True,
581+
**kwargs)
582+
538583

539584
class PyGSPGraph(with_metaclass(abc.ABCMeta, pygsp.graphs.Graph, Base)):
540585
"""Interface between BaseGraph and PyGSP.

0 commit comments

Comments
 (0)