void.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. from __future__ import annotations
  2. import collections
  3. from typing import DefaultDict, Dict, Optional, Set
  4. from rdflib.graph import Graph, _ObjectType, _PredicateType, _SubjectType
  5. from rdflib.namespace import RDF, VOID
  6. from rdflib.term import IdentifiedNode, Literal, URIRef
  7. def generateVoID( # noqa: N802
  8. g: Graph,
  9. dataset: Optional[IdentifiedNode] = None,
  10. res: Optional[Graph] = None,
  11. distinctForPartitions: bool = True, # noqa: N803
  12. ):
  13. """Returns a new graph with a VoID description of the passed dataset
  14. For more info on Vocabulary of Interlinked Datasets (VoID), see:
  15. http://vocab.deri.ie/void
  16. This only makes two passes through the triples (once to detect the types
  17. of things)
  18. The tradeoff is that lots of temporary structures are built up in memory
  19. meaning lots of memory may be consumed :)
  20. I imagine at least a few copies of your original graph.
  21. the distinctForPartitions parameter controls whether
  22. distinctSubjects/objects are tracked for each class/propertyPartition
  23. this requires more memory again
  24. """
  25. typeMap: Dict[_SubjectType, Set[_SubjectType]] = ( # noqa: N806
  26. collections.defaultdict(set)
  27. )
  28. classes: Dict[_ObjectType, Set[_SubjectType]] = collections.defaultdict(set)
  29. for e, c in g.subject_objects(RDF.type):
  30. classes[c].add(e)
  31. typeMap[e].add(c)
  32. triples = 0
  33. subjects: Set[_SubjectType] = set()
  34. objects: Set[_ObjectType] = set()
  35. properties: Set[_PredicateType] = set()
  36. classCount: DefaultDict[_SubjectType, int] = collections.defaultdict( # noqa: N806
  37. int
  38. )
  39. propCount: DefaultDict[_PredicateType, int] = collections.defaultdict( # noqa: N806
  40. int
  41. )
  42. classProps = collections.defaultdict(set) # noqa: N806
  43. classObjects = collections.defaultdict(set) # noqa: N806
  44. propSubjects = collections.defaultdict(set) # noqa: N806
  45. propObjects = collections.defaultdict(set) # noqa: N806
  46. for s, p, o in g:
  47. triples += 1
  48. subjects.add(s)
  49. properties.add(p)
  50. objects.add(o)
  51. # class partitions
  52. if s in typeMap:
  53. for c in typeMap[s]:
  54. classCount[c] += 1
  55. if distinctForPartitions:
  56. classObjects[c].add(o)
  57. classProps[c].add(p)
  58. # property partitions
  59. propCount[p] += 1
  60. if distinctForPartitions:
  61. propObjects[p].add(o)
  62. propSubjects[p].add(s)
  63. if not dataset:
  64. dataset = URIRef("http://example.org/Dataset")
  65. if not res:
  66. res = Graph()
  67. res.add((dataset, RDF.type, VOID.Dataset))
  68. # basic stats
  69. res.add((dataset, VOID.triples, Literal(triples)))
  70. res.add((dataset, VOID.classes, Literal(len(classes))))
  71. res.add((dataset, VOID.distinctObjects, Literal(len(objects))))
  72. res.add((dataset, VOID.distinctSubjects, Literal(len(subjects))))
  73. res.add((dataset, VOID.properties, Literal(len(properties))))
  74. for i, c in enumerate(classes):
  75. part = URIRef(dataset + "_class%d" % i)
  76. res.add((dataset, VOID.classPartition, part))
  77. res.add((part, RDF.type, VOID.Dataset))
  78. res.add((part, VOID.triples, Literal(classCount[c])))
  79. res.add((part, VOID.classes, Literal(1)))
  80. res.add((part, VOID["class"], c))
  81. res.add((part, VOID.entities, Literal(len(classes[c]))))
  82. res.add((part, VOID.distinctSubjects, Literal(len(classes[c]))))
  83. if distinctForPartitions:
  84. res.add((part, VOID.properties, Literal(len(classProps[c]))))
  85. res.add((part, VOID.distinctObjects, Literal(len(classObjects[c]))))
  86. for i, p in enumerate(properties):
  87. part = URIRef(dataset + "_property%d" % i)
  88. res.add((dataset, VOID.propertyPartition, part))
  89. res.add((part, RDF.type, VOID.Dataset))
  90. res.add((part, VOID.triples, Literal(propCount[p])))
  91. res.add((part, VOID.properties, Literal(1)))
  92. res.add((part, VOID.property, p))
  93. if distinctForPartitions:
  94. entities = 0
  95. propClasses = set() # noqa: N806
  96. for s in propSubjects[p]:
  97. if s in typeMap:
  98. entities += 1
  99. for c in typeMap[s]:
  100. propClasses.add(c)
  101. res.add((part, VOID.entities, Literal(entities)))
  102. res.add((part, VOID.classes, Literal(len(propClasses))))
  103. res.add((part, VOID.distinctSubjects, Literal(len(propSubjects[p]))))
  104. res.add((part, VOID.distinctObjects, Literal(len(propObjects[p]))))
  105. return res, dataset