gboeing/osmnx

Dictionaries attributes not handled in io.load_graphml()

ncotie opened this issue · 5 comments

ncotie commented

Contributing guidelines

  • I understand the contributing guidelines

Documentation

  • My problem is not addressed by the documentation or examples

Existing issues

  • My problem does not appear in an existing issue

What operating system and Python version are you using?

Win11, Python 3.10.10

What OSMnx version are you using?

1.6.0

Environment packages and versions

#
# Name                    Version                   Build  Channel
affine                    2.4.0              pyhd8ed1ab_0    conda-forge
anyio                     3.6.2              pyhd8ed1ab_0    conda-forge
argon2-cffi               21.3.0             pyhd8ed1ab_0    conda-forge
argon2-cffi-bindings      21.2.0          py310h8d17308_3    conda-forge
asttokens                 2.2.1              pyhd8ed1ab_0    conda-forge
attrs                     23.1.0             pyh71513ae_1    conda-forge
babel                     2.12.1             pyhd8ed1ab_1    conda-forge
backcall                  0.2.0              pyh9f0ad1d_0    conda-forge
backports                 1.0                pyhd8ed1ab_3    conda-forge
backports.functools_lru_cache 1.6.4              pyhd8ed1ab_0    conda-forge
beautifulsoup4            4.12.2             pyha770c72_0    conda-forge
bleach                    6.0.0              pyhd8ed1ab_0    conda-forge
blosc                     1.21.3               hdccc3a2_0    conda-forge
boost-cpp                 1.78.0               h9f4b32c_3    conda-forge
branca                    0.6.0              pyhd8ed1ab_0    conda-forge
brotli                    1.0.9                hcfcfb64_8    conda-forge
brotli-bin                1.0.9                hcfcfb64_8    conda-forge
brotlipy                  0.7.0           py310h8d17308_1005    conda-forge
bzip2                     1.0.8                h8ffe710_4    conda-forge
ca-certificates           2023.7.22            h56e8100_0    conda-forge
cairo                     1.16.0            hdecc03f_1015    conda-forge
certifi                   2023.7.22          pyhd8ed1ab_0    conda-forge
cffi                      1.15.1          py310h628cb3f_3    conda-forge
cfitsio                   4.2.0                h9ebe7e4_0    conda-forge
charset-normalizer        3.1.0              pyhd8ed1ab_0    conda-forge
click                     8.1.3           win_pyhd8ed1ab_2    conda-forge
click-plugins             1.1.1                      py_0    conda-forge
cligj                     0.7.2              pyhd8ed1ab_1    conda-forge
colorama                  0.4.6              pyhd8ed1ab_0    conda-forge
comm                      0.1.3              pyhd8ed1ab_0    conda-forge
contourpy                 1.0.7           py310h232114e_0    conda-forge
cryptography              40.0.2          py310h6e82f81_0    conda-forge
curl                      8.0.1                h68f0423_0    conda-forge
cycler                    0.11.0             pyhd8ed1ab_0    conda-forge
debugpy                   1.6.7           py310h00ffb61_0    conda-forge
decorator                 5.1.1              pyhd8ed1ab_0    conda-forge
defusedxml                0.7.1              pyhd8ed1ab_0    conda-forge
dill                      0.3.7              pyhd8ed1ab_0    conda-forge
entrypoints               0.4                pyhd8ed1ab_0    conda-forge
executing                 1.2.0              pyhd8ed1ab_0    conda-forge
expat                     2.5.0                h63175ca_1    conda-forge
fiona                     1.9.3           py310h4a685fe_0    conda-forge
flit-core                 3.8.0              pyhd8ed1ab_0    conda-forge
folium                    0.14.0             pyhd8ed1ab_0    conda-forge
font-ttf-dejavu-sans-mono 2.37                 hab24e00_0    conda-forge
font-ttf-inconsolata      3.000                h77eed37_0    conda-forge
font-ttf-source-code-pro  2.038                h77eed37_0    conda-forge
font-ttf-ubuntu           0.83                 hab24e00_0    conda-forge
fontconfig                2.14.2               hbde0cde_0    conda-forge
fonts-conda-ecosystem     1                             0    conda-forge
fonts-conda-forge         1                             0    conda-forge
fonttools                 4.39.3          py310h8d17308_0    conda-forge
freetype                  2.12.1               h546665d_1    conda-forge
freexl                    1.0.6                h67ca5e6_1    conda-forge
gdal                      3.6.4           py310h3ccc0e8_1    conda-forge
geodatasets               2023.3.0           pyhd8ed1ab_0    conda-forge
geopandas                 0.13.0             pyhd8ed1ab_0    conda-forge
geopandas-base            0.13.0             pyha770c72_0    conda-forge
geos                      3.11.2               h1537add_0    conda-forge
geotiff                   1.7.1                h7222e44_8    conda-forge
gettext                   0.21.1               h5728263_0    conda-forge
glpk                      5.0                  h8ffe710_0    conda-forge
hdf4                      4.2.15               h1334946_6    conda-forge
hdf5                      1.14.0          nompi_h918d9b7_103    conda-forge
icu                       72.1                 h63175ca_0    conda-forge
idna                      3.4                pyhd8ed1ab_0    conda-forge
igraph                    0.10.4               hc93da9c_2    conda-forge
importlib-metadata        6.6.0              pyha770c72_0    conda-forge
importlib_metadata        6.6.0                hd8ed1ab_0    conda-forge
importlib_resources       5.12.0             pyhd8ed1ab_0    conda-forge
intel-openmp              2023.1.0         h57928b3_46319    conda-forge
ipykernel                 6.22.0             pyh025b116_0    conda-forge
ipython                   8.13.2             pyh08f2357_0    conda-forge
ipython_genutils          0.2.0                      py_1    conda-forge
jedi                      0.18.2             pyhd8ed1ab_0    conda-forge
jinja2                    3.1.2              pyhd8ed1ab_1    conda-forge
joblib                    1.2.0              pyhd8ed1ab_0    conda-forge
json5                     0.9.5              pyh9f0ad1d_0    conda-forge
jsonschema                4.17.3             pyhd8ed1ab_0    conda-forge
jupyter_client            8.2.0              pyhd8ed1ab_0    conda-forge
jupyter_core              5.3.0           py310h5588dad_0    conda-forge
jupyter_events            0.6.3              pyhd8ed1ab_0    conda-forge
jupyter_server            2.5.0              pyhd8ed1ab_0    conda-forge
jupyter_server_terminals  0.4.4              pyhd8ed1ab_1    conda-forge
jupyterlab                3.5.3              pyhd8ed1ab_0    conda-forge
jupyterlab_pygments       0.2.2              pyhd8ed1ab_0    conda-forge
jupyterlab_server         2.22.1             pyhd8ed1ab_0    conda-forge
kealib                    1.5.0                h2e80506_1    conda-forge
kiwisolver                1.4.4           py310h232114e_1    conda-forge
krb5                      1.20.1               heb0366b_0    conda-forge
lcms2                     2.15                 h3e3b177_1    conda-forge
lerc                      4.0.0                h63175ca_0    conda-forge
libaec                    1.0.6                h63175ca_1    conda-forge
libblas                   3.9.0              16_win64_mkl    conda-forge
libbrotlicommon           1.0.9                hcfcfb64_8    conda-forge
libbrotlidec              1.0.9                hcfcfb64_8    conda-forge
libbrotlienc              1.0.9                hcfcfb64_8    conda-forge
libcblas                  3.9.0              16_win64_mkl    conda-forge
libcurl                   8.0.1                h68f0423_0    conda-forge
libdeflate                1.18                 hcfcfb64_0    conda-forge
libexpat                  2.5.0                h63175ca_1    conda-forge
libffi                    3.4.2                h8ffe710_5    conda-forge
libgdal                   3.6.4                h69cf491_1    conda-forge
libglib                   2.76.2               he8f3873_0    conda-forge
libhwloc                  2.9.1                h51c2c0f_0    conda-forge
libiconv                  1.17                 h8ffe710_0    conda-forge
libjpeg-turbo             2.1.5.1              hcfcfb64_0    conda-forge
libkml                    1.3.0             hf2ab4e4_1015    conda-forge
liblapack                 3.9.0              16_win64_mkl    conda-forge
liblapacke                3.9.0              16_win64_mkl    conda-forge
libnetcdf                 4.9.2           nompi_hc664c2b_104    conda-forge
libpng                    1.6.39               h19919ed_0    conda-forge
libpq                     15.2                 ha9684e8_0    conda-forge
librttopo                 1.1.0               he1da8c1_13    conda-forge
libsodium                 1.0.18               h8d14728_1    conda-forge
libspatialindex           1.9.3                h39d44d4_4    conda-forge
libspatialite             5.0.1               h50a8ebb_25    conda-forge
libsqlite                 3.40.0               hcfcfb64_1    conda-forge
libssh2                   1.10.0               h9a1e1f7_3    conda-forge
libtiff                   4.5.0                h6c8260b_6    conda-forge
libwebp-base              1.3.0                hcfcfb64_0    conda-forge
libxcb                    1.13              hcd874cb_1004    conda-forge
libxml2                   2.10.4               hc3477c8_0    conda-forge
libzip                    1.9.2                h519de47_1    conda-forge
libzlib                   1.2.13               hcfcfb64_4    conda-forge
lz4-c                     1.9.4                hcfcfb64_0    conda-forge
m2w64-gcc-libgfortran     5.3.0                         6    conda-forge
m2w64-gcc-libs            5.3.0                         7    conda-forge
m2w64-gcc-libs-core       5.3.0                         7    conda-forge
m2w64-gmp                 6.1.0                         2    conda-forge
m2w64-libwinpthread-git   5.0.0.4634.697f757               2    conda-forge
mapclassify               2.5.0              pyhd8ed1ab_1    conda-forge
markupsafe                2.1.2           py310h8d17308_0    conda-forge
matplotlib-base           3.7.1           py310h51140c5_0    conda-forge
matplotlib-inline         0.1.6              pyhd8ed1ab_0    conda-forge
mistune                   2.0.5              pyhd8ed1ab_0    conda-forge
mkl                       2022.1.0           h6a75c08_874    conda-forge
mpir                      3.0.0             he025d50_1002    conda-forge
msys2-conda-epoch         20160418                      1    conda-forge
multiprocess              0.70.14         py310h8d17308_3    conda-forge
munch                     2.5.0                      py_0    conda-forge
munkres                   1.1.4              pyh9f0ad1d_0    conda-forge
nbclassic                 1.0.0              pyh8b2e9e2_0    conda-forge
nbclient                  0.7.4              pyhd8ed1ab_0    conda-forge
nbconvert-core            7.3.1              pyhd8ed1ab_0    conda-forge
nbformat                  5.8.0              pyhd8ed1ab_0    conda-forge
nest-asyncio              1.5.6              pyhd8ed1ab_0    conda-forge
networkx                  3.1                pyhd8ed1ab_0    conda-forge
notebook                  6.5.4              pyha770c72_0    conda-forge
notebook-shim             0.2.3              pyhd8ed1ab_0    conda-forge
numpy                     1.24.3          py310hd02465a_0    conda-forge
openjpeg                  2.5.0                ha2aaf27_2    conda-forge
openssl                   3.1.3                hcfcfb64_0    conda-forge
osmnx                     1.6.0              pyhd8ed1ab_0    conda-forge
packaging                 23.1               pyhd8ed1ab_0    conda-forge
pandas                    2.0.1           py310h1c4a608_0    conda-forge
pandocfilters             1.5.0              pyhd8ed1ab_0    conda-forge
parso                     0.8.3              pyhd8ed1ab_0    conda-forge
pcre2                     10.40                h17e33f8_0    conda-forge
pickleshare               0.7.5                   py_1003    conda-forge
pillow                    9.5.0           py310h3dcae36_0    conda-forge
pip                       23.1.2             pyhd8ed1ab_0    conda-forge
pixman                    0.40.0               h8ffe710_0    conda-forge
pkgutil-resolve-name      1.3.10             pyhd8ed1ab_0    conda-forge
platformdirs              3.5.0              pyhd8ed1ab_0    conda-forge
pooch                     1.7.0              pyha770c72_3    conda-forge
poppler                   23.04.0              h934c637_1    conda-forge
poppler-data              0.4.12               hd8ed1ab_0    conda-forge
postgresql                15.2                 hd87cd2b_0    conda-forge
proj                      9.2.0                heca977f_0    conda-forge
prometheus_client         0.16.0             pyhd8ed1ab_0    conda-forge
prompt-toolkit            3.0.38             pyha770c72_0    conda-forge
prompt_toolkit            3.0.38               hd8ed1ab_0    conda-forge
psutil                    5.9.5           py310h8d17308_0    conda-forge
pthread-stubs             0.4               hcd874cb_1001    conda-forge
pthreads-win32            2.9.1                hfa6e2cd_3    conda-forge
pure_eval                 0.2.2              pyhd8ed1ab_0    conda-forge
pycparser                 2.21               pyhd8ed1ab_0    conda-forge
pygments                  2.15.1             pyhd8ed1ab_0    conda-forge
pyopenssl                 23.1.1             pyhd8ed1ab_0    conda-forge
pyparsing                 3.0.9              pyhd8ed1ab_0    conda-forge
pyproj                    3.5.0           py310ha4ef3e2_1    conda-forge
pyrsistent                0.19.3          py310h8d17308_0    conda-forge
pysocks                   1.7.1              pyh0701188_6    conda-forge
python                    3.10.10         h4de0772_0_cpython    conda-forge
python-dateutil           2.8.2              pyhd8ed1ab_0    conda-forge
python-fastjsonschema     2.16.3             pyhd8ed1ab_0    conda-forge
python-igraph             0.10.4          py310h4d9ddb3_0    conda-forge
python-json-logger        2.0.7              pyhd8ed1ab_0    conda-forge
python-tzdata             2023.3             pyhd8ed1ab_0    conda-forge
python_abi                3.10                    3_cp310    conda-forge
pytz                      2023.3             pyhd8ed1ab_0    conda-forge
pywin32                   304             py310h00ffb61_2    conda-forge
pywinpty                  2.0.10          py310h00ffb61_0    conda-forge
pyyaml                    6.0             py310h8d17308_5    conda-forge
pyzmq                     25.0.2          py310hcd737a0_0    conda-forge
rasterio                  1.3.6           py310h0534870_1    conda-forge
requests                  2.29.0             pyhd8ed1ab_0    conda-forge
rfc3339-validator         0.1.4              pyhd8ed1ab_0    conda-forge
rfc3986-validator         0.1.1              pyh9f0ad1d_0    conda-forge
rtree                     1.0.1           py310h1cbd46b_1    conda-forge
scikit-learn              1.2.2           py310hd266714_1    conda-forge
scipy                     1.10.1          py310h578b7cb_1    conda-forge
send2trash                1.8.2              pyh08f2357_0    conda-forge
setuptools                67.7.2             pyhd8ed1ab_0    conda-forge
shapely                   2.0.1           py310h91617ff_1    conda-forge
six                       1.16.0             pyh6c4a22f_0    conda-forge
snappy                    1.1.10               hfb803bf_0    conda-forge
sniffio                   1.3.0              pyhd8ed1ab_0    conda-forge
snuggs                    1.4.7                      py_0    conda-forge
sortedcollections         2.1.0              pyhd8ed1ab_0    conda-forge
sortedcontainers          2.4.0              pyhd8ed1ab_0    conda-forge
soupsieve                 2.3.2.post1        pyhd8ed1ab_0    conda-forge
sqlite                    3.40.0               hcfcfb64_1    conda-forge
stack_data                0.6.2              pyhd8ed1ab_0    conda-forge
suitesparse               5.4.0                h5d0cbe0_1    conda-forge
tbb                       2021.9.0             h91493d7_0    conda-forge
terminado                 0.15.0          py310h5588dad_0    conda-forge
texttable                 1.7.0              pyhd8ed1ab_0    conda-forge
threadpoolctl             3.1.0              pyh8a188c0_0    conda-forge
tiledb                    2.13.2               h3132609_0    conda-forge
tinycss2                  1.2.1              pyhd8ed1ab_0    conda-forge
tk                        8.6.12               h8ffe710_0    conda-forge
tomli                     2.0.1              pyhd8ed1ab_0    conda-forge
tornado                   6.3             py310h8d17308_0    conda-forge
traitlets                 5.9.0              pyhd8ed1ab_0    conda-forge
typing-extensions         4.5.0                hd8ed1ab_0    conda-forge
typing_extensions         4.5.0              pyha770c72_0    conda-forge
tzdata                    2023c                h71feb2d_0    conda-forge
ucrt                      10.0.22621.0         h57928b3_0    conda-forge
unicodedata2              15.0.0          py310h8d17308_0    conda-forge
urllib3                   1.26.15            pyhd8ed1ab_0    conda-forge
vc                        14.3                hb25d44b_16    conda-forge
vc14_runtime              14.34.31931         h5081d32_16    conda-forge
vs2015_runtime            14.34.31931         hed1258a_16    conda-forge
wcwidth                   0.2.6              pyhd8ed1ab_0    conda-forge
webencodings              0.5.1                      py_1    conda-forge
websocket-client          1.5.1              pyhd8ed1ab_0    conda-forge
wheel                     0.40.0             pyhd8ed1ab_0    conda-forge
win_inet_pton             1.1.0              pyhd8ed1ab_6    conda-forge
winpty                    0.4.3                         4    conda-forge
xerces-c                  3.2.4                h63175ca_2    conda-forge
xorg-libxau               1.0.9                hcd874cb_0    conda-forge
xorg-libxdmcp             1.1.3                hcd874cb_0    conda-forge
xyzservices               2023.2.0           pyhd8ed1ab_0    conda-forge
xz                        5.2.6                h8d14728_0    conda-forge
yaml                      0.2.5                h8ffe710_2    conda-forge
zeromq                    4.3.4                h0e60522_1    conda-forge
zipp                      3.15.0             pyhd8ed1ab_0    conda-forge
zlib                      1.2.13               hcfcfb64_4    conda-forge
zstd                      1.5.2                h12be248_6    conda-forge

How did you install OSMnx?

Conda and conda-forge

Problem description

Hello Geoff,

Similar to issue #665 for bool types, _convert_node_attr_types() and _convert_edge_attr_types() can't interpret attributes of type dict stored as strings in a graphml object being loaded using load_graphml(). It throws a "ValueError: dictionary update sequence element #0 has length 1;...", when type 'dict' is provided in node_dtypes or edge_dtypes, e.g. {'amenity_counts': dict} for a custom attribute 'amenity_counts' of type dict.

_convert_edge_attr_types() already has code to handle lists, checking for '[' and ']' characters then using ast.literal_eval(), and it appears that a similar addition could be made for both functions that also included checks for '{' and '}'.

I am successfully using the workaround you suggested in #665, to use dtype ast.eval_literal instead of dtype dict for these attributes.

Thanks
Neil

Complete minimal reproducible example

import networkx as nx
import osmnx as ox

nodes = [1,2,3]
edges = [[1,2], [1,3]]
G = nx.MultiGraph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)

G.edges[1,2, 0]['test_dict_attr'] = {'foo':'bar'}
 # Comment the below line out to show error in edge loading
G.nodes[1]['test_dict_attr'] = {'foo':'bar'} 

ox.save_graphml(G, filepath = 'testsave')

ox.load_graphml(filepath = 'testsave',
                node_dtypes = {'test_dict_attr':dict},
                edge_dtypes = {'test_dict_attr':dict})

To save everything to GraphML, OSMnx stringifies the attribute values. When it reloads the GraphML, the node_dtypes argument (and its peers) are used to determine how to convert each stringified attribute value back to its correct, original type.

For example, if you wanted to have a floating point type for some custom attribute, you'd specify node_dtypes = {"custom_attr": float} because float("123.45") will convert that string value to a float value.

However, it works differently for a dictionary, because of how Python works. That is, dict("{'foo':'bar'}") will not produce a dictionary, but rather the ValueError you noted above. Instead, to convert a string-representation of a dictionary to a dictionary, you would run ast.literal_eval("{'foo':'bar'}").

So, in your code example, the converters you're looking for would be something like:

import ast
ox.load_graphml(filepath = 'testsave.graphml',
                node_dtypes = {'test_dict_attr': ast.literal_eval},
                edge_dtypes = {'test_dict_attr': ast.literal_eval})
ncotie commented

Exactly, that's the workaround that I'm using, that I referred to. For my purposes (custom attributes for processing metrics) it works fine.

I thought I should raise the issue, given that the description for load_graphml doesn't limit support to a specific subset of basic dtypes, so a user could assume that all should work.

I could readily imagine you might want to restrict code support to dtypes that exist in OSM, for example, if that would exclude dict, and I assume that that is why you have coded support for bool and list dtypes. Totally up to you, of course, how to approach this question, whether to extend code or just to document.

Regards
Neil

I could readily imagine you might want to restrict code support to dtypes that exist in OSM

That is the current logic, yes, in that we handle types that exist in OSM plus types we need to make OSMnx's graph building work (ie, Python lists to handle simplified edges' attributes). That said, it has always been a bit confusing regarding when a user needs to use literal_eval. It seems like handling dicts/sets (ie, any attribute value that starts with { and ends with }) might be a graceful compromise solution between simplicity and predictability.

It should be fairly simple to add this by just expanding the logic that already exists in the io module to handle list conversion. If you'd like to take a quick stab at a PR, I could take a look.

ncotie commented

Geoff, I've opened a draft PR with proposed changes. This is my first 'real-world' PR so my apologies if I've gotten things messed up. I wasn't sure how far to get into the hooks and test runs as described in the contribution guideline, but I am assuming that is if I were to be actually executing the merge (which I certainly don't think I'm to do?). I did test the changes by installing from my github into an env and running simple tests of the sort I described here above.
All feedback on what I should have done differently most welcome...
Regards
Neil

Closed by #1075