Advertisement
Guest User

Untitled

a guest
Jun 27th, 2019
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.66 KB | None | 0 0
  1. import matplotlib.pylab as plt
  2. import importlib
  3. import pandas as pd
  4. import numpy as np
  5. from typing import Callable, Iterator
  6. import inspect
  7. from types import ModuleType
  8.  
  9.  
  10. class Required:
  11. def __repr__(self):
  12. return '_REQUIRED_'
  13.  
  14.  
  15. _REQUIRED_ = Required()
  16.  
  17. callable_filt_for = {
  18. 'callable': callable,
  19. 'class': inspect.isclass,
  20. 'function': inspect.isfunction,
  21. 'function_or_class': lambda x: inspect.isclass(x) or inspect.isfunction(x)
  22. }
  23.  
  24. DFLT_CALLABLE_FILT = 'callable' # other choices: inspect.isfunction, inspect.isclass
  25.  
  26.  
  27. def get_callable_filt(callable_filt):
  28. """
  29. Returns a boolean function that will be used to filter objects.
  30. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  31. :return:
  32. """
  33. if isinstance(callable_filt, str):
  34. callable_filt = callable_filt_for.get(callable_filt, None)
  35. if callable_filt is None:
  36. raise ValueError(f"No such callable_filt: {callable_filt}")
  37. return callable_filt
  38.  
  39.  
  40. def module_classes(module):
  41. return filter(inspect.isclass, module.__dict__.values())
  42.  
  43.  
  44. def callables_of_module(module, callable_filt=DFLT_CALLABLE_FILT):
  45. """
  46.  
  47. :param module:
  48. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  49. :return:
  50. """
  51. callable_filt = get_callable_filt(callable_filt)
  52. if isinstance(module, str):
  53. module = importlib.import_module(module)
  54. return (x[1] for x in inspect.getmembers(module, predicate=callable_filt))
  55.  
  56.  
  57. def callables_of_class(cls, callable_filt='function'):
  58. """
  59.  
  60. :param cls:
  61. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  62. :return:
  63. """
  64. callable_filt = get_callable_filt(callable_filt)
  65. return (x[1] for x in inspect.getmembers(cls, predicate=callable_filt))
  66.  
  67.  
  68. def get_callables_from(callables, callable_filt=DFLT_CALLABLE_FILT):
  69. """
  70. Get a generator of callable objects from the input x.
  71. :param callables: A module (or module dot-path string), class, callable, or list thereof
  72. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  73. :return: A generator of callable objects
  74. """
  75. callable_filt = get_callable_filt(callable_filt)
  76. if callable(callables):
  77. if not inspect.isclass(callables):
  78. yield from [callables]
  79. else:
  80. yield from get_callables_from(callables_of_class(callables), callable_filt=callable_filt)
  81. else:
  82. if isinstance(callables, str) or isinstance(callables, ModuleType):
  83. yield from get_callables_from(callables_of_module(callables), callable_filt=callable_filt)
  84. elif inspect.isclass(callables):
  85. yield callables
  86. yield from get_callables_from(callables_of_class(callables), callable_filt=callable_filt)
  87. else:
  88. try:
  89. for c in callables:
  90. if not hasattr(c, '__qualname__'):
  91. continue # hack to avoid a case I don't understand
  92. yield from get_callables_from(c, callable_filt=callable_filt)
  93. except:
  94. raise TypeError(f"Don't know how to handle the input: {c}\n"
  95. "Wasn't a callable, string, module, or iterable of such.")
  96.  
  97.  
  98. class SignatureExtractor:
  99. def __init__(self, normalize_var_names=True):
  100. def param_mint(param):
  101. if normalize_var_names:
  102. if param.kind == inspect.Parameter.VAR_KEYWORD:
  103. d = {'name': '**kwargs'}
  104. elif param.kind == inspect.Parameter.VAR_POSITIONAL:
  105. d = {'name': '*args'}
  106. else:
  107. d = {'name': param.name}
  108. else:
  109. d = {'name': param.name}
  110.  
  111. if param.default == inspect._empty:
  112. d['default'] = _REQUIRED_
  113. else:
  114. d['default'] = param.default
  115.  
  116. return d
  117.  
  118. self.param_mint = param_mint
  119.  
  120. def __call__(self, obj):
  121. return [self.param_mint(p) for p in inspect.signature(obj).parameters.values()]
  122.  
  123.  
  124. # Note: A function would make more sense, but chose to make a class to demo how classes are handled as callables
  125. extract_name_and_default = SignatureExtractor(normalize_var_names=True)
  126.  
  127.  
  128. def name_arg_default_dict_of_callables(callables: Iterator[Callable]) -> dict:
  129. """
  130. Get an {callable_name: {arg_name: arg_default, ...}, ...} dict from a collection of callables.
  131. See also: name_arg_default_dict_of_callables and arg_default_dict_of_module_classes
  132. :param callables: Iterable of callables
  133. :return: A dict
  134. """
  135. d = dict()
  136. for obj in callables:
  137. try:
  138. d[obj.__qualname__] = {x['name']: x['default'] for x in extract_name_and_default(obj)}
  139. except Exception as e:
  140. pass # TODO: Give choice to warn instead of ignore
  141. return d
  142.  
  143.  
  144. def arg_default_dict_of_callables(callables, callable_filt=DFLT_CALLABLE_FILT) -> dict:
  145. """
  146. Get an {callable_name: {arg_name: arg_default, ...}, ...} dict from a collection of callables taken from
  147. a specification of callables.
  148. :param callables = get_callables_from(callables, callable_filt=callable_filt)
  149. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  150. :return: A dict
  151. """
  152. callable_filt = get_callable_filt(callable_filt)
  153. return name_arg_default_dict_of_callables(get_callables_from(callables, callable_filt=callable_filt))
  154.  
  155.  
  156. def non_null_counts(df: pd.DataFrame, null_val=np.nan):
  157. if null_val is np.nan:
  158. non_null_lidx = ~df.isna()
  159. else:
  160. non_null_lidx = df != null_val
  161. row_null_zero_count = non_null_lidx.sum(axis=1)
  162. col_null_zero_count = non_null_lidx.sum(axis=0)
  163. return row_null_zero_count, col_null_zero_count
  164.  
  165.  
  166. def _df_of_callable_arg_default_dict(callable_arg_default_dict, null_fill='') -> pd.DataFrame:
  167. """
  168. Get a dataframe from a callable_arg_default_dict
  169. :param module:
  170. :param null_fill:
  171. :return:
  172. """
  173. d = pd.DataFrame.from_dict(callable_arg_default_dict)
  174. row_null_zero_count, col_null_zero_count = non_null_counts(d, null_val=np.nan)
  175. row_argsort = np.argsort(row_null_zero_count)[::-1]
  176. col_argsort = np.argsort(col_null_zero_count)[::-1]
  177. return d.iloc[row_argsort, col_argsort].fillna(null_fill).T
  178.  
  179.  
  180. def callables_signatures_df(
  181. callables, callable_filt=DFLT_CALLABLE_FILT, null_fill='') -> pd.DataFrame:
  182. """
  183. Get a dataframe representing the signatures of the input callables.
  184. :param callables = get_callables_from(callables, callable_filt=callable_filt)
  185. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  186. :param null_fill: What to fill the empty cells with
  187. :return: A dataframe
  188. """
  189. callable_filt = get_callable_filt(callable_filt)
  190. d = arg_default_dict_of_callables(callables, callable_filt=callable_filt)
  191. return _df_of_callable_arg_default_dict(d, null_fill=null_fill)
  192.  
  193.  
  194. def heatmap(X, y=None, col_labels=None, figsize=None, cmap=None, return_gcf=False, ax=None,
  195. xlabel_top=True, ylabel_left=True, xlabel_bottom=True, ylabel_right=True, **kwargs):
  196. """
  197. Make a heatmap of a matrix or pandas.DataFrame, but let the function figure stuff out.
  198. """
  199. import pandas as pd
  200. import numpy as np
  201. n_items, n_cols = X.shape
  202. if col_labels is not None:
  203. if col_labels is not False:
  204. assert len(col_labels) == n_cols, \
  205. "col_labels length should be the same as the number of columns in the matrix"
  206. elif isinstance(X, pd.DataFrame):
  207. col_labels = list(X.columns)
  208.  
  209. if figsize is None:
  210. x_size, y_size = X.shape
  211. if x_size >= y_size:
  212. figsize = (6, min(18, 6 * x_size / y_size))
  213. else:
  214. figsize = (min(18, 6 * y_size / x_size), 6)
  215.  
  216. if cmap is None:
  217. if X.min(axis=0).min(axis=0) < 0:
  218. cmap = 'RdBu_r'
  219. else:
  220. cmap = 'hot_r'
  221.  
  222. kwargs['cmap'] = cmap
  223. kwargs = dict(kwargs, interpolation='nearest', aspect='auto')
  224.  
  225. if figsize is not False:
  226. plt.figure(figsize=figsize)
  227.  
  228. if ax is None:
  229. plt.imshow(X, **kwargs)
  230. else:
  231. ax.imshow(X, **kwargs)
  232. plt.grid(None)
  233.  
  234. if y is not None:
  235. y = np.array(y)
  236. assert all(sorted(y) == y), "This will only work if your row_labels are sorted"
  237.  
  238. unik_ys, unik_ys_idx = np.unique(y, return_index=True)
  239. for u, i in zip(unik_ys, unik_ys_idx):
  240. plt.hlines(i - 0.5, 0 - 0.5, n_cols - 0.5, colors='b', linestyles='dotted', alpha=0.5)
  241. plt.hlines(n_items - 0.5, 0 - 0.5, n_cols - 0.5, colors='b', linestyles='dotted', alpha=0.5)
  242. plt.yticks(unik_ys_idx + np.diff(np.hstack((unik_ys_idx, n_items))) / 2, unik_ys)
  243. elif isinstance(X, pd.DataFrame):
  244. y_tick_labels = list(X.index)
  245. plt.yticks(list(range(len(y_tick_labels))), y_tick_labels);
  246.  
  247. if col_labels is not None:
  248. plt.xticks(list(range(len(col_labels))), col_labels)
  249. else:
  250. plt.xticks([])
  251.  
  252. plt.gca().xaxis.set_tick_params(labeltop=xlabel_top, labelbottom=xlabel_bottom)
  253. plt.gca().yaxis.set_tick_params(labelleft=ylabel_left, labelright=ylabel_right)
  254.  
  255. if return_gcf:
  256. return plt.gcf()
  257.  
  258.  
  259. def heatmap_of_signatures(callables, callable_filt=DFLT_CALLABLE_FILT, figsize=None, cmap='gray_r'):
  260. """
  261. Visualize a matrix containing the all functions of the module, and their arguments.
  262. :param callables: A module (or module dot-path string), class, callable, or list thereof
  263. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  264. :param figsize: Size of the figure (will try to figure one out if not specified - pun noticed)
  265. :return:
  266. """
  267. callable_filt = get_callable_filt(callable_filt)
  268. callables = get_callables_from(callables, callable_filt=callable_filt)
  269. core_comp_df = callables_signatures_df(callables, callable_filt=callable_filt)
  270. t = (core_comp_df != '').astype(int) + (core_comp_df == _REQUIRED_).astype(int)
  271. heatmap(t, figsize=figsize, cmap=cmap)
  272. plt.xticks(rotation=90)
  273. plt.grid(False)
  274. plt.show()
  275.  
  276.  
  277. def plot_nonnull_counts_of_signatures(
  278. callables, callable_filt=DFLT_CALLABLE_FILT, n_top_items=50, figsize=None, hspace=0.5):
  279. """
  280. For all callables of the input module, will plot (as a bar graph):
  281. The argument count of each callable
  282. The callable count of each argument (i.e., in how many callables are the given argument used)
  283. :param callables: A module (or module dot-path string), class, callable, or list thereof
  284. :param callable_filt: A boolean function or the strings 'callable', 'class', 'function', or 'function_or_class'
  285. :param n_top_items: Number of (top) items to plot
  286. :param figsize: Size of the figure (will try to figure one out if not specified - pun noticed)
  287. :param hspace: The space between both bar plots.
  288. :return:
  289. """
  290. callable_filt = get_callable_filt(callable_filt)
  291. callables = get_callables_from(callables, callable_filt=callable_filt)
  292. core_comp_df = callables_signatures_df(callables, callable_filt=callable_filt)
  293. t, tt = non_null_counts(core_comp_df, null_val='')
  294. plt.figure(figsize=figsize)
  295. plt.subplot(2, 1, 1)
  296. t.iloc[:n_top_items].plot(kind='bar')
  297. plt.subplot(2, 1, 2)
  298. tt.iloc[:n_top_items].plot(kind='bar')
  299. plt.subplots_adjust(hspace=hspace)
  300. plt.show()
  301.  
  302.  
  303. if __name__ == '__main__':
  304. import argh
  305.  
  306. argh.dispatch_commands([callables_signatures_df,
  307. heatmap_of_signatures,
  308. plot_nonnull_counts_of_signatures])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement