risk-network 0.0.11__py3-none-any.whl → 0.0.12b1__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.
Files changed (41) hide show
  1. risk/__init__.py +1 -1
  2. risk/annotations/__init__.py +4 -1
  3. risk/annotations/io.py +48 -47
  4. risk/annotations/nltk_setup.py +2 -1
  5. risk/log/__init__.py +1 -1
  6. risk/log/parameters.py +21 -22
  7. risk/neighborhoods/__init__.py +0 -1
  8. risk/neighborhoods/api.py +2 -2
  9. risk/neighborhoods/community.py +33 -4
  10. risk/neighborhoods/domains.py +6 -4
  11. risk/neighborhoods/neighborhoods.py +7 -1
  12. risk/neighborhoods/stats/__init__.py +13 -0
  13. risk/neighborhoods/stats/permutation/__init__.py +6 -0
  14. risk/{stats → neighborhoods/stats}/permutation/permutation.py +7 -4
  15. risk/{stats → neighborhoods/stats}/permutation/test_functions.py +2 -2
  16. risk/{stats/stat_tests.py → neighborhoods/stats/tests.py} +21 -13
  17. risk/network/__init__.py +0 -2
  18. risk/network/graph/__init__.py +0 -2
  19. risk/network/graph/api.py +2 -2
  20. risk/network/graph/graph.py +56 -57
  21. risk/{stats/significance.py → network/graph/stats.py} +2 -2
  22. risk/network/graph/summary.py +2 -3
  23. risk/network/io.py +151 -8
  24. risk/network/plotter/__init__.py +0 -2
  25. risk/network/plotter/api.py +1 -1
  26. risk/network/plotter/canvas.py +35 -35
  27. risk/network/plotter/contour.py +11 -12
  28. risk/network/plotter/labels.py +257 -246
  29. risk/network/plotter/plotter.py +2 -4
  30. risk/network/plotter/utils/colors.py +3 -0
  31. risk/risk.py +5 -5
  32. risk_network-0.0.12b1.dist-info/METADATA +122 -0
  33. risk_network-0.0.12b1.dist-info/RECORD +40 -0
  34. {risk_network-0.0.11.dist-info → risk_network-0.0.12b1.dist-info}/WHEEL +1 -1
  35. risk/network/geometry.py +0 -150
  36. risk/stats/__init__.py +0 -15
  37. risk/stats/permutation/__init__.py +0 -6
  38. risk_network-0.0.11.dist-info/METADATA +0 -798
  39. risk_network-0.0.11.dist-info/RECORD +0 -41
  40. {risk_network-0.0.11.dist-info → risk_network-0.0.12b1.dist-info/licenses}/LICENSE +0 -0
  41. {risk_network-0.0.11.dist-info → risk_network-0.0.12b1.dist-info}/top_level.txt +0 -0
@@ -188,7 +188,7 @@ class Labels:
188
188
  # Calculate the bounding box around the network
189
189
  center, radius = calculate_bounding_box(self.graph.node_coordinates, radius_margin=scale)
190
190
  # Calculate the best positions for labels
191
- best_label_positions = _calculate_best_label_positions(
191
+ best_label_positions = self._calculate_best_label_positions(
192
192
  filtered_domain_centroids, center, radius, offset
193
193
  )
194
194
  # Convert all domain colors to RGBA using the to_rgba helper function
@@ -207,7 +207,9 @@ class Labels:
207
207
  # Split by special key TERM_DELIMITER to split annotation into multiple lines
208
208
  annotations = filtered_domain_terms[domain].split(TERM_DELIMITER)
209
209
  if fontcase is not None:
210
- annotations = _apply_str_transformation(words=annotations, transformation=fontcase)
210
+ annotations = self._apply_str_transformation(
211
+ words=annotations, transformation=fontcase
212
+ )
211
213
  self.ax.annotate(
212
214
  "\n".join(annotations),
213
215
  xy=centroid,
@@ -281,6 +283,9 @@ class Labels:
281
283
  found in arrow_alpha. Defaults to 1.0.
282
284
  arrow_base_shrink (float, optional): Distance between the text and the base of the arrow. Defaults to 0.0.
283
285
  arrow_tip_shrink (float, optional): Distance between the arrow tip and the centroid. Defaults to 0.0.
286
+
287
+ Raises:
288
+ ValueError: If no nodes are found in the network graph or if there are insufficient nodes to plot.
284
289
  """
285
290
  # Check if nodes is a list of lists or a flat list
286
291
  if any(isinstance(item, (list, tuple, np.ndarray)) for item in nodes):
@@ -627,7 +632,7 @@ class Labels:
627
632
  ]
628
633
 
629
634
  # Use the combine_words function directly to handle word combinations and length constraints
630
- compressed_terms = _combine_words(tuple(terms), max_chars_per_line, max_label_lines)
635
+ compressed_terms = self._combine_words(tuple(terms), max_chars_per_line, max_label_lines)
631
636
 
632
637
  return compressed_terms
633
638
 
@@ -676,249 +681,255 @@ class Labels:
676
681
  random_seed=random_seed,
677
682
  )
678
683
 
684
+ def _combine_words(
685
+ self, words: List[str], max_chars_per_line: int, max_label_lines: int
686
+ ) -> str:
687
+ """Combine words to fit within the max_chars_per_line and max_label_lines constraints,
688
+ and separate the final output by TERM_DELIMITER for plotting.
679
689
 
680
- def _combine_words(words: List[str], max_chars_per_line: int, max_label_lines: int) -> str:
681
- """Combine words to fit within the max_chars_per_line and max_label_lines constraints,
682
- and separate the final output by TERM_DELIMITER for plotting.
683
-
684
- Args:
685
- words (List[str]): List of words to combine.
686
- max_chars_per_line (int): Maximum number of characters in a line to display.
687
- max_label_lines (int): Maximum number of lines in a label.
688
-
689
- Returns:
690
- str: String of combined words separated by ':' for line breaks.
691
- """
692
-
693
- def try_combinations(words_batch: List[str]) -> List[str]:
694
- """Try to combine words within a batch and return them with combined words separated by ':'."""
695
- combined_lines = []
696
- i = 0
697
- while i < len(words_batch):
698
- current_word = words_batch[i]
699
- combined_word = current_word # Start with the current word
700
- # Try to combine more words if possible, and ensure the combination fits within max_length
701
- for j in range(i + 1, len(words_batch)):
702
- next_word = words_batch[j]
703
- # Ensure that the combined word fits within the max_chars_per_line limit
704
- if len(combined_word) + len(next_word) + 1 <= max_chars_per_line: # +1 for space
705
- combined_word = f"{combined_word} {next_word}"
706
- i += 1 # Move past the combined word
707
- else:
708
- break # Stop combining if the length is exceeded
709
-
710
- # Add the combined word only if it fits within the max_chars_per_line limit
711
- if len(combined_word) <= max_chars_per_line:
712
- combined_lines.append(combined_word) # Add the combined word
713
- # Move to the next word
714
- i += 1
715
-
716
- # Stop if we've reached the max_label_lines limit
717
- if len(combined_lines) >= max_label_lines:
718
- break
719
-
720
- return combined_lines
721
-
722
- # Main logic: start with max_label_lines number of words
723
- combined_lines = try_combinations(words[:max_label_lines])
724
- remaining_words = words[max_label_lines:] # Remaining words after the initial batch
725
- # Track words that have already been added
726
- existing_words = set(" ".join(combined_lines).split())
727
-
728
- # Continue pulling more words until we fill the lines
729
- while remaining_words and len(combined_lines) < max_label_lines:
730
- available_slots = max_label_lines - len(combined_lines)
731
- words_to_add = [
732
- word for word in remaining_words[:available_slots] if word not in existing_words
690
+ Args:
691
+ words (List[str]): List of words to combine.
692
+ max_chars_per_line (int): Maximum number of characters in a line to display.
693
+ max_label_lines (int): Maximum number of lines in a label.
694
+
695
+ Returns:
696
+ str: String of combined words separated by ':' for line breaks.
697
+ """
698
+
699
+ def try_combinations(words_batch: List[str]) -> List[str]:
700
+ """Try to combine words within a batch and return them with combined words separated by ':'."""
701
+ combined_lines = []
702
+ i = 0
703
+ while i < len(words_batch):
704
+ current_word = words_batch[i]
705
+ combined_word = current_word # Start with the current word
706
+ # Try to combine more words if possible, and ensure the combination fits within max_length
707
+ for j in range(i + 1, len(words_batch)):
708
+ next_word = words_batch[j]
709
+ # Ensure that the combined word fits within the max_chars_per_line limit
710
+ if (
711
+ len(combined_word) + len(next_word) + 1 <= max_chars_per_line
712
+ ): # +1 for space
713
+ combined_word = f"{combined_word} {next_word}"
714
+ i += 1 # Move past the combined word
715
+ else:
716
+ break # Stop combining if the length is exceeded
717
+
718
+ # Add the combined word only if it fits within the max_chars_per_line limit
719
+ if len(combined_word) <= max_chars_per_line:
720
+ combined_lines.append(combined_word) # Add the combined word
721
+ # Move to the next word
722
+ i += 1
723
+
724
+ # Stop if we've reached the max_label_lines limit
725
+ if len(combined_lines) >= max_label_lines:
726
+ break
727
+
728
+ return combined_lines
729
+
730
+ # Main logic: start with max_label_lines number of words
731
+ combined_lines = try_combinations(words[:max_label_lines])
732
+ remaining_words = words[max_label_lines:] # Remaining words after the initial batch
733
+ # Track words that have already been added
734
+ existing_words = set(" ".join(combined_lines).split())
735
+
736
+ # Continue pulling more words until we fill the lines
737
+ while remaining_words and len(combined_lines) < max_label_lines:
738
+ available_slots = max_label_lines - len(combined_lines)
739
+ words_to_add = [
740
+ word for word in remaining_words[:available_slots] if word not in existing_words
741
+ ]
742
+ remaining_words = remaining_words[available_slots:]
743
+ # Update the existing words set
744
+ existing_words.update(words_to_add)
745
+ # Add to combined_lines only unique words
746
+ combined_lines += try_combinations(words_to_add)
747
+
748
+ # Join the final combined lines with TERM_DELIMITER, a special separator for line breaks
749
+ return TERM_DELIMITER.join(combined_lines[:max_label_lines])
750
+
751
+ def _calculate_best_label_positions(
752
+ self,
753
+ filtered_domain_centroids: Dict[str, Any],
754
+ center: np.ndarray,
755
+ radius: float,
756
+ offset: float,
757
+ ) -> Dict[str, Any]:
758
+ """Calculate and optimize label positions for clarity.
759
+
760
+ Args:
761
+ filtered_domain_centroids (Dict[str, Any]): Centroids of the filtered domains.
762
+ center (np.ndarray): The center coordinates for label positioning.
763
+ radius (float): The radius for positioning labels around the center.
764
+ offset (float): The offset distance from the radius for positioning labels.
765
+
766
+ Returns:
767
+ Dict[str, Any]: Optimized positions for labels.
768
+ """
769
+ num_domains = len(filtered_domain_centroids)
770
+ # Calculate equidistant positions around the center for initial label placement
771
+ equidistant_positions = self._calculate_equidistant_positions_around_center(
772
+ center, radius, offset, num_domains
773
+ )
774
+ # Create a mapping of domains to their initial label positions
775
+ label_positions = dict(zip(filtered_domain_centroids.keys(), equidistant_positions))
776
+ # Optimize the label positions to minimize distance to domain centroids
777
+ return self._optimize_label_positions(label_positions, filtered_domain_centroids)
778
+
779
+ def _calculate_equidistant_positions_around_center(
780
+ self, center: np.ndarray, radius: float, label_offset: float, num_domains: int
781
+ ) -> List[np.ndarray]:
782
+ """Calculate positions around a center at equidistant angles.
783
+
784
+ Args:
785
+ center (np.ndarray): The central point around which positions are calculated.
786
+ radius (float): The radius at which positions are calculated.
787
+ label_offset (float): The offset added to the radius for label positioning.
788
+ num_domains (int): The number of positions (or domains) to calculate.
789
+
790
+ Returns:
791
+ List[np.ndarray]: List of positions (as 2D numpy arrays) around the center.
792
+ """
793
+ # Calculate equidistant angles in radians around the center
794
+ angles = np.linspace(0, 2 * np.pi, num_domains, endpoint=False)
795
+ # Compute the positions around the center using the angles
796
+ return [
797
+ center + (radius + label_offset) * np.array([np.cos(angle), np.sin(angle)])
798
+ for angle in angles
733
799
  ]
734
- remaining_words = remaining_words[available_slots:]
735
- # Update the existing words set
736
- existing_words.update(words_to_add)
737
- # Add to combined_lines only unique words
738
- combined_lines += try_combinations(words_to_add)
739
-
740
- # Join the final combined lines with TERM_DELIMITER, a special separator for line breaks
741
- return TERM_DELIMITER.join(combined_lines[:max_label_lines])
742
-
743
-
744
- def _calculate_best_label_positions(
745
- filtered_domain_centroids: Dict[str, Any], center: np.ndarray, radius: float, offset: float
746
- ) -> Dict[str, Any]:
747
- """Calculate and optimize label positions for clarity.
748
-
749
- Args:
750
- filtered_domain_centroids (Dict[str, Any]): Centroids of the filtered domains.
751
- center (np.ndarray): The center coordinates for label positioning.
752
- radius (float): The radius for positioning labels around the center.
753
- offset (float): The offset distance from the radius for positioning labels.
754
-
755
- Returns:
756
- Dict[str, Any]: Optimized positions for labels.
757
- """
758
- num_domains = len(filtered_domain_centroids)
759
- # Calculate equidistant positions around the center for initial label placement
760
- equidistant_positions = _calculate_equidistant_positions_around_center(
761
- center, radius, offset, num_domains
762
- )
763
- # Create a mapping of domains to their initial label positions
764
- label_positions = dict(zip(filtered_domain_centroids.keys(), equidistant_positions))
765
- # Optimize the label positions to minimize distance to domain centroids
766
- return _optimize_label_positions(label_positions, filtered_domain_centroids)
767
-
768
-
769
- def _calculate_equidistant_positions_around_center(
770
- center: np.ndarray, radius: float, label_offset: float, num_domains: int
771
- ) -> List[np.ndarray]:
772
- """Calculate positions around a center at equidistant angles.
773
-
774
- Args:
775
- center (np.ndarray): The central point around which positions are calculated.
776
- radius (float): The radius at which positions are calculated.
777
- label_offset (float): The offset added to the radius for label positioning.
778
- num_domains (int): The number of positions (or domains) to calculate.
779
-
780
- Returns:
781
- List[np.ndarray]: List of positions (as 2D numpy arrays) around the center.
782
- """
783
- # Calculate equidistant angles in radians around the center
784
- angles = np.linspace(0, 2 * np.pi, num_domains, endpoint=False)
785
- # Compute the positions around the center using the angles
786
- return [
787
- center + (radius + label_offset) * np.array([np.cos(angle), np.sin(angle)])
788
- for angle in angles
789
- ]
790
-
791
-
792
- def _optimize_label_positions(
793
- best_label_positions: Dict[str, Any], domain_centroids: Dict[str, Any]
794
- ) -> Dict[str, Any]:
795
- """Optimize label positions around the perimeter to minimize total distance to centroids.
796
-
797
- Args:
798
- best_label_positions (Dict[str, Any]): Initial positions of labels around the perimeter.
799
- domain_centroids (Dict[str, Any]): Centroid positions of the domains.
800
-
801
- Returns:
802
- Dict[str, Any]: Optimized label positions.
803
- """
804
- while True:
805
- improvement = False # Start each iteration assuming no improvement
806
- # Iterate through each pair of labels to check for potential improvements
807
- for i in range(len(domain_centroids)):
808
- for j in range(i + 1, len(domain_centroids)):
809
- # Calculate the current total distance
810
- current_distance = _calculate_total_distance(best_label_positions, domain_centroids)
811
- # Evaluate the total distance after swapping two labels
812
- swapped_distance = _swap_and_evaluate(best_label_positions, i, j, domain_centroids)
813
- # If the swap improves the total distance, perform the swap
814
- if swapped_distance < current_distance:
815
- labels = list(best_label_positions.keys())
816
- best_label_positions[labels[i]], best_label_positions[labels[j]] = (
817
- best_label_positions[labels[j]],
818
- best_label_positions[labels[i]],
800
+
801
+ def _optimize_label_positions(
802
+ self, best_label_positions: Dict[str, Any], domain_centroids: Dict[str, Any]
803
+ ) -> Dict[str, Any]:
804
+ """Optimize label positions around the perimeter to minimize total distance to centroids.
805
+
806
+ Args:
807
+ best_label_positions (Dict[str, Any]): Initial positions of labels around the perimeter.
808
+ domain_centroids (Dict[str, Any]): Centroid positions of the domains.
809
+
810
+ Returns:
811
+ Dict[str, Any]: Optimized label positions.
812
+ """
813
+ while True:
814
+ improvement = False # Start each iteration assuming no improvement
815
+ # Iterate through each pair of labels to check for potential improvements
816
+ for i in range(len(domain_centroids)):
817
+ for j in range(i + 1, len(domain_centroids)):
818
+ # Calculate the current total distance
819
+ current_distance = self._calculate_total_distance(
820
+ best_label_positions, domain_centroids
819
821
  )
820
- improvement = True # Found an improvement, so continue optimizing
821
-
822
- if not improvement:
823
- break # Exit the loop if no improvement was found in this iteration
824
-
825
- return best_label_positions
826
-
827
-
828
- def _calculate_total_distance(
829
- label_positions: Dict[str, Any], domain_centroids: Dict[str, Any]
830
- ) -> float:
831
- """Calculate the total distance from label positions to their domain centroids.
832
-
833
- Args:
834
- label_positions (Dict[str, Any]): Positions of labels around the perimeter.
835
- domain_centroids (Dict[str, Any]): Centroid positions of the domains.
836
-
837
- Returns:
838
- float: The total distance from labels to centroids.
839
- """
840
- total_distance = 0
841
- # Iterate through each domain and calculate the distance to its centroid
842
- for domain, pos in label_positions.items():
843
- centroid = domain_centroids[domain]
844
- total_distance += np.linalg.norm(centroid - pos)
845
-
846
- return total_distance
847
-
848
-
849
- def _swap_and_evaluate(
850
- label_positions: Dict[str, Any],
851
- i: int,
852
- j: int,
853
- domain_centroids: Dict[str, Any],
854
- ) -> float:
855
- """Swap two labels and evaluate the total distance after the swap.
856
-
857
- Args:
858
- label_positions (Dict[str, Any]): Positions of labels around the perimeter.
859
- i (int): Index of the first label to swap.
860
- j (int): Index of the second label to swap.
861
- domain_centroids (Dict[str, Any]): Centroid positions of the domains.
862
-
863
- Returns:
864
- float: The total distance after swapping the two labels.
865
- """
866
- # Get the list of labels from the dictionary keys
867
- labels = list(label_positions.keys())
868
- swapped_positions = copy.deepcopy(label_positions)
869
- # Swap the positions of the two specified labels
870
- swapped_positions[labels[i]], swapped_positions[labels[j]] = (
871
- swapped_positions[labels[j]],
872
- swapped_positions[labels[i]],
873
- )
874
- # Calculate and return the total distance after the swap
875
- return _calculate_total_distance(swapped_positions, domain_centroids)
876
-
877
-
878
- def _apply_str_transformation(
879
- words: List[str], transformation: Union[str, Dict[str, str]]
880
- ) -> List[str]:
881
- """Apply a user-specified case transformation to each word in the list without appending duplicates.
882
-
883
- Args:
884
- words (List[str]): A list of words to transform.
885
- transformation (Union[str, Dict[str, str]]): A single transformation (e.g., 'lower', 'upper', 'title', 'capitalize')
886
- or a dictionary mapping cases ('lower', 'upper', 'title') to transformations (e.g., 'lower'='upper').
887
-
888
- Returns:
889
- List[str]: A list of transformed words with no duplicates.
890
- """
891
- # Initialize a list to store transformed words
892
- transformed_words = []
893
- for word in words:
894
- # Split word into subwords by space
895
- subwords = word.split(" ")
896
- transformed_subwords = []
897
- # Apply transformation to each subword
898
- for subword in subwords:
899
- transformed_subword = subword # Start with the original subword
900
- # If transformation is a string, apply it to all subwords
901
- if isinstance(transformation, str):
902
- if hasattr(subword, transformation):
903
- transformed_subword = getattr(subword, transformation)()
904
-
905
- # If transformation is a dictionary, apply case-specific transformations
906
- elif isinstance(transformation, dict):
907
- for case_type, transform in transformation.items():
908
- if case_type == "lower" and subword.islower() and transform:
909
- transformed_subword = getattr(subword, transform)()
910
- elif case_type == "upper" and subword.isupper() and transform:
911
- transformed_subword = getattr(subword, transform)()
912
- elif case_type == "title" and subword.istitle() and transform:
913
- transformed_subword = getattr(subword, transform)()
914
-
915
- # Append the transformed subword to the list
916
- transformed_subwords.append(transformed_subword)
917
-
918
- # Rejoin the transformed subwords into a single string to preserve structure
919
- transformed_word = " ".join(transformed_subwords)
920
- # Only append if the transformed word is not already in the list
921
- if transformed_word not in transformed_words:
922
- transformed_words.append(transformed_word)
923
-
924
- return transformed_words
822
+ # Evaluate the total distance after swapping two labels
823
+ swapped_distance = self._swap_and_evaluate(
824
+ best_label_positions, i, j, domain_centroids
825
+ )
826
+ # If the swap improves the total distance, perform the swap
827
+ if swapped_distance < current_distance:
828
+ labels = list(best_label_positions.keys())
829
+ best_label_positions[labels[i]], best_label_positions[labels[j]] = (
830
+ best_label_positions[labels[j]],
831
+ best_label_positions[labels[i]],
832
+ )
833
+ improvement = True # Found an improvement, so continue optimizing
834
+
835
+ if not improvement:
836
+ break # Exit the loop if no improvement was found in this iteration
837
+
838
+ return best_label_positions
839
+
840
+ def _calculate_total_distance(
841
+ self, label_positions: Dict[str, Any], domain_centroids: Dict[str, Any]
842
+ ) -> float:
843
+ """Calculate the total distance from label positions to their domain centroids.
844
+
845
+ Args:
846
+ label_positions (Dict[str, Any]): Positions of labels around the perimeter.
847
+ domain_centroids (Dict[str, Any]): Centroid positions of the domains.
848
+
849
+ Returns:
850
+ float: The total distance from labels to centroids.
851
+ """
852
+ total_distance = 0
853
+ # Iterate through each domain and calculate the distance to its centroid
854
+ for domain, pos in label_positions.items():
855
+ centroid = domain_centroids[domain]
856
+ total_distance += np.linalg.norm(centroid - pos)
857
+
858
+ return total_distance
859
+
860
+ def _swap_and_evaluate(
861
+ self,
862
+ label_positions: Dict[str, Any],
863
+ i: int,
864
+ j: int,
865
+ domain_centroids: Dict[str, Any],
866
+ ) -> float:
867
+ """Swap two labels and evaluate the total distance after the swap.
868
+
869
+ Args:
870
+ label_positions (Dict[str, Any]): Positions of labels around the perimeter.
871
+ i (int): Index of the first label to swap.
872
+ j (int): Index of the second label to swap.
873
+ domain_centroids (Dict[str, Any]): Centroid positions of the domains.
874
+
875
+ Returns:
876
+ float: The total distance after swapping the two labels.
877
+ """
878
+ # Get the list of labels from the dictionary keys
879
+ labels = list(label_positions.keys())
880
+ swapped_positions = copy.deepcopy(label_positions)
881
+ # Swap the positions of the two specified labels
882
+ swapped_positions[labels[i]], swapped_positions[labels[j]] = (
883
+ swapped_positions[labels[j]],
884
+ swapped_positions[labels[i]],
885
+ )
886
+ # Calculate and return the total distance after the swap
887
+ return self._calculate_total_distance(swapped_positions, domain_centroids)
888
+
889
+ def _apply_str_transformation(
890
+ self, words: List[str], transformation: Union[str, Dict[str, str]]
891
+ ) -> List[str]:
892
+ """Apply a user-specified case transformation to each word in the list without appending duplicates.
893
+
894
+ Args:
895
+ words (List[str]): A list of words to transform.
896
+ transformation (Union[str, Dict[str, str]]): A single transformation (e.g., 'lower', 'upper', 'title', 'capitalize')
897
+ or a dictionary mapping cases ('lower', 'upper', 'title') to transformations (e.g., 'lower'='upper').
898
+
899
+ Returns:
900
+ List[str]: A list of transformed words with no duplicates.
901
+ """
902
+ # Initialize a list to store transformed words
903
+ transformed_words = []
904
+ for word in words:
905
+ # Split word into subwords by space
906
+ subwords = word.split(" ")
907
+ transformed_subwords = []
908
+ # Apply transformation to each subword
909
+ for subword in subwords:
910
+ transformed_subword = subword # Start with the original subword
911
+ # If transformation is a string, apply it to all subwords
912
+ if isinstance(transformation, str):
913
+ if hasattr(subword, transformation):
914
+ transformed_subword = getattr(subword, transformation)()
915
+
916
+ # If transformation is a dictionary, apply case-specific transformations
917
+ elif isinstance(transformation, dict):
918
+ for case_type, transform in transformation.items():
919
+ if case_type == "lower" and subword.islower() and transform:
920
+ transformed_subword = getattr(subword, transform)()
921
+ elif case_type == "upper" and subword.isupper() and transform:
922
+ transformed_subword = getattr(subword, transform)()
923
+ elif case_type == "title" and subword.istitle() and transform:
924
+ transformed_subword = getattr(subword, transform)()
925
+
926
+ # Append the transformed subword to the list
927
+ transformed_subwords.append(transformed_subword)
928
+
929
+ # Rejoin the transformed subwords into a single string to preserve structure
930
+ transformed_word = " ".join(transformed_subwords)
931
+ # Only append if the transformed word is not already in the list
932
+ if transformed_word not in transformed_words:
933
+ transformed_words.append(transformed_word)
934
+
935
+ return transformed_words
@@ -114,8 +114,7 @@ class Plotter(Canvas, Network, Contour, Labels):
114
114
 
115
115
  return ax
116
116
 
117
- @staticmethod
118
- def savefig(*args, pad_inches: float = 0.5, dpi: int = 100, **kwargs) -> None:
117
+ def savefig(self, *args, pad_inches: float = 0.5, dpi: int = 100, **kwargs) -> None:
119
118
  """Save the current plot to a file with additional export options.
120
119
 
121
120
  Args:
@@ -132,8 +131,7 @@ class Plotter(Canvas, Network, Contour, Labels):
132
131
  # Call plt.savefig with combined arguments
133
132
  plt.savefig(*args, **kwargs)
134
133
 
135
- @staticmethod
136
- def show(*args, **kwargs) -> None:
134
+ def show(self, *args, **kwargs) -> None:
137
135
  """Display the current plot.
138
136
 
139
137
  Args:
@@ -356,6 +356,9 @@ def to_rgba(
356
356
 
357
357
  Returns:
358
358
  np.ndarray: Array of RGBA colors repeated `num_repeats` times, if applicable.
359
+
360
+ Raises:
361
+ ValueError: If the provided color format is invalid or cannot be converted to RGBA.
359
362
  """
360
363
 
361
364
  def convert_to_rgba(c: Union[str, List, Tuple, np.ndarray]) -> np.ndarray:
risk/risk.py CHANGED
@@ -3,12 +3,12 @@ risk/risk
3
3
  ~~~~~~~~~
4
4
  """
5
5
 
6
- from risk.annotations import AnnotationsIO
6
+ from risk.annotations.io import AnnotationsIO
7
7
  from risk.log import params, set_global_verbosity
8
- from risk.neighborhoods import NeighborhoodsAPI
9
- from risk.network import NetworkIO
10
- from risk.network.graph import GraphAPI
11
- from risk.network.plotter import PlotterAPI
8
+ from risk.neighborhoods.api import NeighborhoodsAPI
9
+ from risk.network.graph.api import GraphAPI
10
+ from risk.network.io import NetworkIO
11
+ from risk.network.plotter.api import PlotterAPI
12
12
 
13
13
 
14
14
  class RISK(NetworkIO, AnnotationsIO, NeighborhoodsAPI, GraphAPI, PlotterAPI):