Skip to content

Commit

Permalink
Merge pull request #30 from BBVA/towards/release_3_2_5
Browse files Browse the repository at this point in the history
Towards/release 3 2 5
  • Loading branch information
sbasaldua authored Feb 4, 2025
2 parents 133d81a + 7fa91eb commit 4902c82
Show file tree
Hide file tree
Showing 16 changed files with 639 additions and 296 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
.vscode
.coverage
**/__pycache__/
**/checkpoint/
**/tmp_checkpoint/
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## Latest version 3.2.4
## Latest version 3.2.5

| Release | Feature link to discussion | State |
| -------- | -------------------------- | ----- |
| 3.2.5 | Fixes Moebius utf-8 issue, adds create_tutorials(), <br>adds generate_color_palette(), improves Moebius example, <br>adds python 3.13 support. | READY |
| 3.2.4 | Implements Moebius as anywidget for modern Jupyterlab | READY |
| 3.2.3 | Fixes Moebius in Google Colab and update docs | READY |
| 3.2.2 | Adds tutorials and fixes doc issues | READY |
Expand Down
4 changes: 0 additions & 4 deletions KNOWN_ISSUES.md

This file was deleted.

2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
include mercury/graph/

graft mercury/graph/

recursive-include tutorials *
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-3816/)
[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-3916/)
[![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-31011/)
[![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3113/)
[![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3119/)
[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3128/)
[![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3131/)
[![Apache 2 license](https://shields.io/badge/license-Apache%202-blue)](http://www.apache.org/licenses/LICENSE-2.0)
[![Ask Me Anything !](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg)](https://github.com/BBVA/mercury-graph/issues)

Expand Down
9 changes: 8 additions & 1 deletion mercury/graph/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
__version__ = '3.2.4'
__version__ = '3.2.5'

from .create_tutorials import create_tutorials

from . import core
from . import embeddings
from . import ml
from . import viz
36 changes: 36 additions & 0 deletions mercury/graph/create_tutorials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os, pkg_resources, shutil


def create_tutorials(destination, silent = False):
"""
Copies mercury.graph tutorial notebooks to `destination`. A folder will be created inside
output_dir, named 'graph_tutorials'. The folder `destination` must exist.
Args:
destination (str): The destination directory
silent (bool): If True, suppresses output on success.
Raises:
ValueError: If `output_dir` is equal to source path.
Examples:
>>> # copy tutorials to /tmp/graph_tutorials
>>> from mercury.graph import create_tutorials
>>> create_tutorials('/tmp')
"""
src = pkg_resources.resource_filename(__package__, 'tutorials')
dst = os.path.abspath(destination)

assert src != dst, 'Destination (%s) cannot be the same as source.' % src

assert os.path.isdir(dst), 'Destination (%s) must be a directory.' % dst

dst = os.path.join(dst, 'graph_tutorials')

assert not os.path.exists(dst), 'Destination (%s) already exists' % dst

shutil.copytree(src, dst)

if not silent:
print('Tutorials copied to: %s' % dst)
2 changes: 1 addition & 1 deletion mercury/graph/ml/spark_randomwalker.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from mercury.graph.core import Graph
from mercury.graph.core.base import BaseClass
from mercury.graph.embeddings.spark_node2vec import udf_select_element_2

from mercury.graph.core.spark_interface import pyspark_installed, graphframes_installed

if pyspark_installed:
import pyspark.sql.functions as f
from pyspark.sql import Window
from mercury.graph.embeddings.spark_node2vec import udf_select_element_2

if graphframes_installed:
from graphframes import GraphFrame
Expand Down
83 changes: 76 additions & 7 deletions mercury/graph/viz/moebius.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class MoebiusAnywidget(anywidget.AnyWidget):
MoebiusAnywidget class for visualizing graphs creating an anywidget that uses JavaScript and HTML.
Note: It is recommended to use wrapper class Moebius.
**Important**: This class requires: anywidget, traitlets and IPython.display!! These packages are not mandatory
for the rest of the library, since you may not be intereseted in visualizing graphs. If you want to use this
class, you need to install these packages.
Args:
G (Graph): The graph to be visualized.
initial_id (str): The id of the node to start the visualization.
Expand Down Expand Up @@ -75,7 +79,7 @@ def __init__(self, G, initial_id = None, initial_depth = 1, node_config = None,
// Set up shared state or event handlers.
return () => {{
// Optional: Called when the widget is destroyed.
}}
}}
}},
render({{ model, el }}) {{
// Render the widget's view into the el HTMLElement.
Expand Down Expand Up @@ -130,6 +134,37 @@ def __getitem__(self, item):
return self._get_adjacent_nodes_moebius(item)


def generate_color_palette(self, cats, hue = 0, sat = 0.7, light = 0.5):
"""
Generates a color palette for the given categories. This can be used in combination with `node_or_edge_config` to generate
the dictionary expected by the `colors` argument with colors that cover the whole 0..1 hue range.
Args:
cats (iterable): An iterable of categories for which the color palette is to be generated.
hue (float, optional): The base hue that is added to all the colors in the color palette. It must be in range, 0..1, all the
resulting hue values will be kept modulo 1.0. Default is 0 (no shift).
sat (float, optional): The saturation level for the colors. Default is 0.7. Range is 0..1.
light (float, optional): The lightness level for the colors. Default is 0.5. Range is 0..1.
Returns:
dict: A dictionary where keys are categories and values are hex color codes.
"""
cats = set(cats)
cols = {}
N = len(cats)
for i, cat in enumerate(cats):
h = (i/N + hue) % 1.0
s = sat
l = light

r, g, b = self._hsl_to_rgb(h, s, l)

hex_color = '#%02x%02X%02x' % (r, g, b)
cols[cat] = hex_color

return cols


def node_or_edge_config(self, text_is = None, color_is = None, colors = None, size_is = None, size_range = None, size_scale = 'linear'):
"""
Create a `node_config` or `edge_config` configuration dictionary for `show()` in an understandable way.
Expand Down Expand Up @@ -260,17 +295,17 @@ def _get_adjacent_nodes_moebius(self, node_id, limit = 20, depth = 1):

if self.use_spark:
json_final = {
'nodes': json.loads(nodes_df.toPandas().to_json(orient = 'records')),
'links': json.loads(edges_df.toPandas().to_json(orient = 'records'))
'nodes': json.loads(nodes_df.toPandas().to_json(orient = 'records', force_ascii = False)),
'links': json.loads(edges_df.toPandas().to_json(orient = 'records', force_ascii = False))
}

else:
json_final = {
'nodes': json.loads(nodes_df.to_json(orient = 'records')),
'links': json.loads(edges_df.to_json(orient = 'records'))
'nodes': json.loads(nodes_df.to_json(orient = 'records', force_ascii = False)),
'links': json.loads(edges_df.to_json(orient = 'records', force_ascii = False))
}

return json.dumps(json_final)
return json.dumps(json_final, ensure_ascii = False)


def _get_one_level_subgraph_graphframes(self, node_id, _testing=False):
Expand Down Expand Up @@ -420,7 +455,7 @@ def _pd_to_json_format(self, df):
json_final = {'nodes' : nodes, 'links' : edges}
return json.dumps(json_final, ensure_ascii=False)
return json.dumps(json_final, ensure_ascii = False)
```
Args:
Expand All @@ -433,10 +468,44 @@ def _pd_to_json_format(self, df):

return json.loads(df_json)


def _hsl_to_rgb(self, h, s, l):
"""
Convert HSL (Hue, Saturation, Lightness) color space to RGB (Red, Green, Blue) color space.
Parameters:
h (float): Hue value, should be between 0 and 1.
s (float): Saturation value, should be between 0 and 1.
l (float): Lightness value, should be between 0 and 1.
Returns:
tuple: A tuple containing the RGB values (r, g, b), each ranging from 0 to 255.
"""
def hue_to_rgb(p, q, t):
if t < 0: t += 1
if t > 1: t -= 1
if t < 1/6: return p + (q - p)*6*t
if t < 1/2: return q
if t < 2/3: return p + (q - p)*(2/3 - t)*6
return p

q = l + s - l*s if l < 0.5 else l + s - l*s
p = 2*l - q
r = hue_to_rgb(p, q, h + 1/3)
g = hue_to_rgb(p, q, h)
b = hue_to_rgb(p, q, h - 1/3)

return int(255*r), int(255*g), int(255*b)


class Moebius(MoebiusAnywidget):
"""
Moebius class for visualizing graphs using an anywidget.
**Important**: This class requires: anywidget, traitlets and IPython.display!! These packages are not mandatory
for the rest of the library, since you may not be intereseted in visualizing graphs. If you want to use this
class, you need to install these packages.
Usage:
```python
from mercury.graph.viz import Moebius
Expand Down
2 changes: 1 addition & 1 deletion mercury/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# The source version file is <proj>/mercury/version.py, anything else is auto generated.
__version__ = '3.2.4'
__version__ = '3.2.5'

9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ build-backend = 'setuptools.build_meta'

[project]
name = 'mercury-graph'
version = '3.2.4'
version = '3.2.5'
description = '.'
license = {file = "LICENSE"}
requires-python = '>=3.8'
classifiers = ['Programming Language :: Python :: 3',
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent']
keywords = ['graph', 'embedding', 'graph embedding', 'graph exploration', 'graph metrics']
keywords = ['graph', 'embedding', 'graph embedding', 'graph exploration', 'graph metrics', 'graph visualization']
authors = [{name = 'Mercury Team', email = '[email protected]'}]
readme = 'README.md'

[tool.setuptools]
include-package-data = true

[tool.setuptools.packages.find]
include = ['mercury*']
include = ['mercury*', 'tutorials*']
exclude = ['docker', 'unit_tests']

[tool.pytest.ini_options]
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ scipy
networkx
scikit-learn
anywidget
traitlets
15 changes: 13 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import os, shutil


# Move tutorials inside mercury.graph before packaging
if os.path.exists('tutorials'):
shutil.move('tutorials', 'mercury/graph/tutorials')


from setuptools import setup, find_packages


setup_args = dict(
packages = find_packages(include='mercury*', exclude=['docker', 'unit_tests']),
scripts = ['test.sh']
name = 'mercury-graph',
packages = find_packages(include = ['mercury*', 'tutorials*'], exclude = ['docker', 'unit_tests']),
scripts = ['test.sh'],
include_package_data = True,
package_data = {'mypackage': ['tutorials/*', 'tutorials/data/*']}
)

setup(**setup_args)
61 changes: 61 additions & 0 deletions tutorials/data/chamberi_aristas.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
src dst weight
La Boutique de Luz Pepe, Dalila & Co 115
Artesanía y Alma EcoVida 87
Juegos y Aventuras Café Serenidad 142
Juegos y Aventuras Yoga y Equilibrio 12
Greenland Madridities 98
Pan y Tradición Greenland 173
Diversión sin Límites Tierra Pura 38
Flores de Chamberí Artesanía y Alma 170
Flores y Colores Galería Inspiración 15
Flores y Colores Artesanía y Alma 151
EcoVida Flores de Chamberí 33
Arte en Vivo Pepe, Dalila & Co 23
Madridities Pan y Tradición 135
Arte en Vivo Moda Urbana 147
Juegos y Aventuras Pepe, Dalila & Co 109
Flores de Chamberí Equilibrio Natural 58
Gambón Hub Madridities 152
Pan y Tradición Moda Urbana 133
Flores y Colores Tierra Pura 186
Rincón del Sabor Equilibrio Natural 64
Rincón del Sabor Creaciones Únicas 81
Horno del Barrio Greenland 162
Moda Urbana Pan y Tradición 182
Edges Café Serenidad 85
Helados del Sol Diversión sin Límites 109
Madridities Gambón Hub 164
Helados del Sol Equilibrio Natural 64
Flores y Colores Artesanía y Alma 150
Gambón Hub Pan y Tradición 74
EcoVida Greenland 200
Horno del Barrio Pan y Tradición 109
Gambón Hub Juegos y Aventuras 80
Dulces Recuerdos Greenland 142
Pan y Tradición Greenland 122
Tierra Pura Equilibrio Natural 152
Equilibrio Natural Rincón del Sabor 33
Madridities Diversión sin Límites 80
Pepe, Dalila & Co Café Serenidad 143
Juegos y Aventuras Diversión sin Límites 163
Rincón del Sabor Dulces Recuerdos 63
EcoVida Café Serenidad 175
Flores de Chamberí La Boutique de Luz 47
Diversión sin Límites EcoVida 143
Juegos y Aventuras Artesanía y Alma 52
Diversión sin Límites Moda Urbana 160
Arte en Vivo Café Serenidad 17
La Boutique de Luz Greenland 179
Helados del Sol Equilibrio Natural 66
Edges Arte en Vivo 194
Creaciones Únicas Yoga y Equilibrio 33
Rincón del Sabor La Boutique de Luz 104
Pepe, Dalila & Co Creaciones Únicas 152
Artesanía y Alma Diversión sin Límites 145
Café Serenidad Artesanía y Alma 98
La Boutique de Luz Artesanía y Alma 193
Yoga y Equilibrio Pan y Tradición 101
Gambón Hub Galería Inspiración 186
Rincón del Sabor Madridities 69
Horno del Barrio Gambón Hub 96
Horno del Barrio Helados del Sol 37
26 changes: 26 additions & 0 deletions tutorials/data/chamberi_nodos.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
id actividad facturación precio_medio
Café Serenidad cafetería 106788 12
La Boutique de Luz boutique 98400 60
EcoVida tienda_ecológica 44300 25
Horno del Barrio panadería 74700 5
Flores de Chamberí floristería 102090 30
Artesanía y Alma mercado_artesanal 54320 40
Galería Inspiración galería_arte 141000 150
Yoga y Equilibrio estudio_yoga 28120 20
Juegos y Aventuras tienda_juegos 102750 50
Helados del Sol heladería 27080 10
Rincón del Sabor cafetería 44688 12
Moda Urbana boutique 164040 60
Tierra Pura tienda_ecológica 80725 25
Pan y Tradición panadería 51960 5
Flores y Colores floristería 90990 30
Creaciones Únicas mercado_artesanal 38400 40
Arte en Vivo galería_arte 74400 150
Equilibrio Natural estudio_yoga 16300 20
Diversión sin Límites tienda_juegos 88650 50
Dulces Recuerdos heladería 34570 10
Edges galería_arte 67800 150
Gambón Hub mercado_artesanal 28360 40
Greenland tienda_ecológica 74525 25
Pepe, Dalila & Co boutique 99480 60
Madridities tienda_juegos 44300 50
Loading

0 comments on commit 4902c82

Please sign in to comment.