py2ls 0.2.4.6__py3-none-any.whl → 0.2.4.8__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.
py2ls/plot.py CHANGED
@@ -1,25 +1,20 @@
1
- import matplotlib.pyplot as plt
2
1
  import numpy as np
3
- import pandas as pd
4
- from matplotlib.colors import to_rgba
5
- from scipy.stats import gaussian_kde
6
- import seaborn as sns
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
7
4
  import matplotlib
8
- import matplotlib.ticker as tck
9
- from cycler import cycler
5
+ import seaborn as sns
6
+ import warnings
10
7
  import logging
11
- import os
12
- import re
13
8
  from typing import Union
14
- from .ips import fsave, fload, mkdir, listdir, figsave, strcmp, unique, get_os, ssplit,flatten,plt_font
9
+ from .ips import isa, fsave, fload, mkdir, listdir, figsave, strcmp, unique, get_os, ssplit,flatten,plt_font,run_once_within
15
10
  from .stats import *
16
- from .netfinder import get_soup, fetch
17
-
11
+ import os
18
12
  # Suppress INFO messages from fontTools
19
13
  logging.getLogger("fontTools").setLevel(logging.ERROR)
20
14
  logging.getLogger('matplotlib').setLevel(logging.ERROR)
21
15
 
22
-
16
+ warnings.simplefilter("ignore", category=pd.errors.SettingWithCopyWarning)
17
+ warnings.filterwarnings("ignore", category=pd.errors.PerformanceWarning)
23
18
 
24
19
  def add_text(ax=None, height_offset=0.5, fmt=".1f", **kwargs):
25
20
  """Adds text annotations for various types of Seaborn and Matplotlib plots.
@@ -452,7 +447,9 @@ def catplot(data, *args, **kwargs):
452
447
  Args:
453
448
  data (array): data matrix
454
449
  """
455
-
450
+ from matplotlib.colors import to_rgba
451
+ import os
452
+
456
453
  def plot_bars(data, data_m, opt_b, xloc, ax, label=None):
457
454
  if "l" in opt_b["loc"]:
458
455
  xloc_s = xloc - opt_b["x_dist"]
@@ -757,6 +754,7 @@ def catplot(data, *args, **kwargs):
757
754
  label=label[i] if label else None,
758
755
  )
759
756
  else:
757
+ from scipy.stats import gaussian_kde
760
758
  kde = gaussian_kde(ys, bw_method=opt_v["BandWidth"])
761
759
  min_val, max_val = ys.min(), ys.max()
762
760
  y_vals = np.linspace(min_val, max_val, opt_v["NumPoints"])
@@ -1814,53 +1812,9 @@ def read_mplstyle(style_file):
1814
1812
  return style_dict
1815
1813
 
1816
1814
 
1817
- def figsets(*args, **kwargs):
1818
- """
1819
- usage:
1820
- figsets(ax=axs[1],
1821
- ylim=[0, 10],
1822
- spine=2,
1823
- xticklabel=['wake','sleep'],
1824
- yticksdddd=np.arange(0,316,60),
1825
- labels_loc=['right','top'],
1826
- ticks=dict(
1827
- ax='x',
1828
- which='minor',
1829
- direction='out',
1830
- width=2,
1831
- length=2,
1832
- c_tick='m',
1833
- pad=5,
1834
- label_size=11),
1835
- grid=dict(which='minor',
1836
- ax='x',
1837
- alpha=.4,
1838
- c='b',
1839
- ls='-.',
1840
- lw=0.75,
1841
- ),
1842
- supertitleddddd=f'sleep druations\n(min)',
1843
- c_spine='r',
1844
- minor_ticks='xy',
1845
- style='paper',
1846
- box=['right','bottom'],
1847
- xrot=-45,
1848
- yangle=20,
1849
- font_sz = 12,
1850
- legend=dict(labels=['group_a','group_b'],
1851
- loc='upper left',
1852
- edgecolor='k',
1853
- facecolor='r',
1854
- title='title',
1855
- fancybox=1,
1856
- shadow=1,
1857
- ncols=4,
1858
- bbox_to_anchor=[-0.5,0.7],
1859
- alignment='left')
1860
- )
1861
- """
1815
+ def figsets(*args, **kwargs):
1862
1816
  import matplotlib
1863
-
1817
+ from cycler import cycler
1864
1818
  matplotlib.rc("text", usetex=False)
1865
1819
 
1866
1820
  fig = plt.gcf()
@@ -1897,7 +1851,16 @@ def figsets(*args, **kwargs):
1897
1851
  nonlocal fontsize, fontname
1898
1852
  if ("fo" in key) and (("size" in key) or ("sz" in key)):
1899
1853
  fontsize = value
1900
- plt.rcParams.update({"font.size": fontsize})
1854
+ plt.rcParams.update({"font.size": fontsize,
1855
+ "figure.titlesize":fontsize,
1856
+ "axes.titlesize":fontsize,
1857
+ "axes.labelsize": fontsize,
1858
+ "xtick.labelsize": fontsize,
1859
+ "ytick.labelsize": fontsize,
1860
+ "legend.fontsize": fontsize,
1861
+ "legend.title_fontsize":fontsize
1862
+ })
1863
+
1901
1864
  # Customize tick labels
1902
1865
  ax.tick_params(axis='both', which='major', labelsize=fontsize)
1903
1866
  for label in ax.get_xticklabels() + ax.get_yticklabels():
@@ -2146,6 +2109,7 @@ def figsets(*args, **kwargs):
2146
2109
  # ])
2147
2110
 
2148
2111
  if "mi" in key.lower() and "tic" in key.lower(): # minor_ticks
2112
+ import matplotlib.ticker as tck
2149
2113
  if "x" in value.lower() or "x" in key.lower():
2150
2114
  ax.xaxis.set_minor_locator(tck.AutoMinorLocator()) # ax.minorticks_on()
2151
2115
  if "y" in value.lower() or "y" in key.lower():
@@ -2255,24 +2219,77 @@ def figsets(*args, **kwargs):
2255
2219
  key = args[ip * 2].lower()
2256
2220
  value = args[ip * 2 + 1]
2257
2221
  set_step_2(ax, key, value)
2258
- colors = [
2259
- "#474747",
2260
- "#FF2C00",
2261
- "#0C5DA5",
2262
- "#845B97",
2263
- "#58BBCC",
2264
- "#FF9500",
2265
- "#D57DBE",
2266
- ]
2222
+
2223
+ colors = get_color(8)
2267
2224
  matplotlib.rcParams["axes.prop_cycle"] = cycler(color=colors)
2268
2225
  if len(fig.get_axes()) > 1:
2269
2226
  plt.tight_layout()
2270
- plt.gcf().align_labels()
2271
2227
 
2228
+ def split_legend(ax, n=2, loc=None, title=None, bbox=None,ncol=1, **kwargs):
2229
+ """
2230
+ split_legend(
2231
+ ax,
2232
+ n=2,
2233
+ loc=["upper left", "lower right"],
2234
+ labelcolor="k",
2235
+ fontsize=6,
2236
+ )
2237
+ """
2238
+ # Retrieve all lines and labels from the axis
2239
+ handles, labels = ax.get_legend_handles_labels()
2240
+ num_labels = len(labels)
2241
+
2242
+ # Calculate the number of labels per legend part
2243
+ labels_per_part = (num_labels + n - 1) // n # Round up
2244
+ # Create a list to hold each legend object
2245
+ legends = []
2246
+
2247
+ # Default locations and titles if not specified
2248
+ if loc is None:
2249
+ loc = ['best'] * n
2250
+ if title is None:
2251
+ title = [None] * n
2252
+ if bbox is None:
2253
+ bbox = [None] * n
2254
+
2255
+ # Loop to create each split legend
2256
+ for i in range(n):
2257
+ # Calculate the range of labels for this part
2258
+ start_idx = i * labels_per_part
2259
+ end_idx = min(start_idx + labels_per_part, num_labels)
2260
+
2261
+ # Skip if no labels in this range
2262
+ if start_idx >= end_idx:
2263
+ break
2272
2264
 
2273
- from cycler import cycler
2274
-
2265
+ # Subset handles and labels
2266
+ part_handles = handles[start_idx:end_idx]
2267
+ part_labels = labels[start_idx:end_idx]
2268
+
2269
+ # Create the legend for this part
2270
+ legend = ax.legend(handles=part_handles,
2271
+ labels=part_labels,
2272
+ loc=loc[i],
2273
+ title=title[i],
2274
+ ncol=ncol,
2275
+ bbox_to_anchor=bbox[i],
2276
+ **kwargs)
2277
+
2278
+ # Add the legend to the axis and save it to the list
2279
+ ax.add_artist(legend) if i !=(n-1) else None # the lastone will be added automaticaly
2280
+ legends.append(legend)
2281
+ return legends
2275
2282
 
2283
+ def get_colors(
2284
+ n: int = 1,
2285
+ cmap: str = "auto",
2286
+ by: str = "start",
2287
+ alpha: float = 1.0,
2288
+ output: str = "hue",
2289
+ *args,
2290
+ **kwargs,
2291
+ ):
2292
+ return get_color(n,cmap,alpha,output,*args,**kwargs)
2276
2293
  def get_color(
2277
2294
  n: int = 1,
2278
2295
  cmap: str = "auto",
@@ -2282,6 +2299,7 @@ def get_color(
2282
2299
  *args,
2283
2300
  **kwargs,
2284
2301
  ):
2302
+ from cycler import cycler
2285
2303
  def cmap2hex(cmap_name):
2286
2304
  cmap_ = matplotlib.pyplot.get_cmap(cmap_name)
2287
2305
  colors = [cmap_(i) for i in range(cmap_.N)]
@@ -2329,81 +2347,86 @@ def get_color(
2329
2347
  return "#{:02X}{:02X}{:02X}{:02X}".format(r, g, b, a)
2330
2348
  else:
2331
2349
  return "#{:02X}{:02X}{:02X}".format(r, g, b)
2332
-
2350
+
2351
+ # sc.pl.palettes.default_20
2352
+ cmap_20= ['#1f77b4','#ff7f0e','#279e68','#d62728','#aa40fc','#8c564b','#e377c2','#b5bd61',
2353
+ '#17becf','#aec7e8','#ffbb78','#98df8a','#ff9896','#c5b0d5','#c49c94','#f7b6d2',
2354
+ '#dbdb8d','#9edae5','#ad494a','#8c6d31']
2355
+ # sc.pl.palettes.zeileis_28
2356
+ cmap_28 = ["#023fa5","#7d87b9","#bec1d4","#d6bcc0","#bb7784","#8e063b","#4a6fe3","#8595e1",
2357
+ "#b5bbe3","#e6afb9","#e07b91","#d33f6a","#11c638","#8dd593","#c6dec7","#ead3c6",
2358
+ "#f0b98d","#ef9708","#0fcfc0","#9cded6","#d5eae7","#f3e1eb","#f6c4e1","#f79cd4",
2359
+ "#7f7f7f","#c7c7c7","#1CE6FF","#336600"]
2333
2360
  if cmap == "gray":
2334
2361
  cmap = "grey"
2362
+ elif cmap=="20":
2363
+ cmap=cmap_20
2364
+ elif cmap=="28":
2365
+ cmap=cmap_28
2335
2366
  # Determine color list based on cmap parameter
2336
- if "aut" in cmap:
2337
- if n == 1:
2338
- colorlist = ["#3A4453"]
2339
- elif n == 2:
2340
- colorlist = ["#3A4453", "#DF5932"]
2341
- elif n == 3:
2342
- colorlist = ["#3A4453", "#DF5932", "#299D8F"]
2343
- elif n == 4:
2344
- # colorlist = ["#3A4453", "#DF5932", "#EBAA00", "#0B4083"]
2345
- colorlist = ["#81C6BD", "#FBAF63", "#F2675B", "#72A1C9"]
2346
- elif n == 5:
2347
- colorlist = [
2348
- "#3A4453",
2349
- "#427AB2",
2350
- "#F09148",
2351
- "#DBDB8D",
2352
- "#C59D94",
2353
- "#AFC7E8",
2354
- ]
2355
- elif n == 6:
2356
- colorlist = [
2357
- "#3A4453",
2358
- "#427AB2",
2359
- "#F09148",
2360
- "#DBDB8D",
2361
- "#C59D94",
2362
- "#E53528",
2363
- ]
2364
- else:
2365
- colorlist = [
2366
- "#474747",
2367
- "#FF2C00",
2368
- "#0C5DA5",
2369
- "#845B97",
2370
- "#58BBCC",
2371
- "#FF9500",
2372
- "#D57DBE",
2373
- ]
2374
- by = "start"
2375
- elif any(["cub" in cmap.lower(), "sns" in cmap.lower()]):
2376
- if kwargs:
2377
- colorlist = sns.cubehelix_palette(n, **kwargs)
2378
- else:
2379
- colorlist = sns.cubehelix_palette(
2380
- n, start=0.5, rot=-0.75, light=0.85, dark=0.15, as_cmap=False
2367
+ if isinstance(cmap,str):
2368
+ if "aut" in cmap:
2369
+ if n == 1:
2370
+ colorlist = ["#3A4453"]
2371
+ elif n == 2:
2372
+ colorlist = ["#3A4453", "#FF2C00"]
2373
+ elif n == 3:
2374
+ colorlist = ["#66c2a5","#fc8d62","#8da0cb"]
2375
+ elif n == 4:
2376
+ colorlist = ["#FF2C00","#087cf7", "#FBAF63", "#3C898A"]
2377
+ elif n == 5:
2378
+ colorlist = ["#FF2C00","#459AA9", "#B25E9D", "#4B8C3B","#EF8632"]
2379
+ elif n == 6:
2380
+ colorlist = ["#FF2C00","#91bfdb", "#B25E9D", "#4B8C3B","#EF8632", "#24578E"]
2381
+ elif n==7:
2382
+ colorlist = ["#7F7F7F", "#459AA9", "#B25E9D", "#4B8C3B","#EF8632", "#24578E" "#FF2C00"]
2383
+ elif n==8:
2384
+ # colorlist = ['#1f77b4','#ff7f0e','#367B7F','#51B34F','#d62728','#aa40fc','#e377c2','#17becf']
2385
+ # colorlist = ["#367C7E","#51B34F","#881A11","#E9374C","#EF893C","#010072","#385DCB","#EA43E3"]
2386
+ colorlist = ["#78BFDA","#D52E6F","#F7D648","#A52D28","#6B9F41","#E18330","#E18B9D","#3C88CC"]
2387
+ elif n==9:
2388
+ colorlist = ['#1f77b4','#ff7f0e','#367B7F','#ff9896','#d62728','#aa40fc','#e377c2','#51B34F','#17becf']
2389
+ elif n==10:
2390
+ colorlist = ['#1f77b4','#ff7f0e','#367B7F','#ff9896','#51B34F','#d62728''#aa40fc','#e377c2','#375FD2','#17becf']
2391
+ elif 10<n<=20:
2392
+ colorlist = cmap_20
2393
+ else:
2394
+ colorlist = cmap_28
2395
+ by = "start"
2396
+ elif any(["cub" in cmap.lower(), "sns" in cmap.lower()]):
2397
+ if kwargs:
2398
+ colorlist = sns.cubehelix_palette(n, **kwargs)
2399
+ else:
2400
+ colorlist = sns.cubehelix_palette(
2401
+ n, start=0.5, rot=-0.75, light=0.85, dark=0.15, as_cmap=False
2402
+ )
2403
+ colorlist = [matplotlib.colors.rgb2hex(color) for color in colorlist]
2404
+ elif any(["hls" in cmap.lower(), "hsl" in cmap.lower()]):
2405
+ if kwargs:
2406
+ colorlist = sns.hls_palette(n, **kwargs)
2407
+ else:
2408
+ colorlist = sns.hls_palette(n)
2409
+ colorlist = [matplotlib.colors.rgb2hex(color) for color in colorlist]
2410
+ elif any(["col" in cmap.lower(), "pal" in cmap.lower()]):
2411
+ palette, desat, as_cmap = None, None, False
2412
+ if kwargs:
2413
+ for k, v in kwargs.items():
2414
+ if "p" in k:
2415
+ palette = v
2416
+ elif "d" in k:
2417
+ desat = v
2418
+ elif "a" in k:
2419
+ as_cmap = v
2420
+ colorlist = sns.color_palette(
2421
+ palette=palette, n_colors=n, desat=desat, as_cmap=as_cmap
2381
2422
  )
2382
- colorlist = [matplotlib.colors.rgb2hex(color) for color in colorlist]
2383
- elif any(["hls" in cmap.lower(), "hsl" in cmap.lower()]):
2384
- if kwargs:
2385
- colorlist = sns.hls_palette(n, **kwargs)
2423
+ colorlist = [matplotlib.colors.rgb2hex(color) for color in colorlist]
2386
2424
  else:
2387
- colorlist = sns.hls_palette(n)
2388
- colorlist = [matplotlib.colors.rgb2hex(color) for color in colorlist]
2389
- elif any(["col" in cmap.lower(), "pal" in cmap.lower()]):
2390
- palette, desat, as_cmap = None, None, False
2391
- if kwargs:
2392
- for k, v in kwargs.items():
2393
- if "p" in k:
2394
- palette = v
2395
- elif "d" in k:
2396
- desat = v
2397
- elif "a" in k:
2398
- as_cmap = v
2399
- colorlist = sns.color_palette(
2400
- palette=palette, n_colors=n, desat=desat, as_cmap=as_cmap
2401
- )
2402
- colorlist = [matplotlib.colors.rgb2hex(color) for color in colorlist]
2403
- else:
2404
- if by == "start":
2405
- by = "linspace"
2406
- colorlist = cmap2hex(cmap)
2425
+ if by == "start":
2426
+ by = "linspace"
2427
+ colorlist = cmap2hex(cmap)
2428
+ elif isinstance(cmap, list):
2429
+ colorlist=cmap
2407
2430
 
2408
2431
  # Determine method for generating color list
2409
2432
  if "st" in by.lower() or "be" in by.lower():
@@ -2423,35 +2446,15 @@ def get_color(
2423
2446
  return hue_list
2424
2447
  else:
2425
2448
  raise ValueError("Invalid output type. Choose 'rgb' or 'hue'.")
2426
-
2427
- # # Example usage
2428
- # colors = get_color(n=5, cmap="viridis", by="linear", alpha=0.5,output='rgb')
2429
- # print(colors)
2430
-
2431
-
2432
- """
2433
- # n = 7
2434
- # clist = get_color(n, cmap="auto", by="linspace") # get_color(100)
2435
- # plt.figure(figsize=[8, 5], dpi=100)
2436
- # x = np.linspace(0, 2 * np.pi, 50) * 100
2437
- # y = np.sin(x)
2438
- # for i in range(1, n + 1):
2439
- # plt.plot(x, y + i, c=clist[i - 1], lw=5, label=str(i))
2440
- # plt.legend()
2441
- # plt.ylim(-2, 20)
2442
- # figsets(plt.gca(), {"style": "whitegrid"}) """
2443
-
2444
-
2445
- from scipy.signal import savgol_filter
2446
- import numpy as np
2447
- import matplotlib.pyplot as plt
2448
-
2449
+
2449
2450
 
2450
2451
  def stdshade(ax=None, *args, **kwargs):
2451
2452
  """
2452
2453
  usage:
2453
2454
  plot.stdshade(data_array, c=clist[1], lw=2, ls="-.", alpha=0.2)
2454
2455
  """
2456
+ from scipy.signal import savgol_filter
2457
+
2455
2458
  # Separate kws_line and kws_fill if necessary
2456
2459
  kws_line = kwargs.pop("kws_line", {})
2457
2460
  kws_fill = kwargs.pop("kws_fill", {})
@@ -2957,6 +2960,7 @@ def thumbnail(dir_img_list: list, figsize=(10, 10), dpi=100, show=False, verbose
2957
2960
 
2958
2961
 
2959
2962
  def get_params_from_func_usage(function_signature):
2963
+ import re
2960
2964
  # Regular expression to match parameter names, ignoring '*' and '**kwargs'
2961
2965
  keys_pattern = r"(?<!\*\*)\b(\w+)="
2962
2966
  # Find all matches
@@ -2983,7 +2987,7 @@ def plotxy(
2983
2987
  x=None,
2984
2988
  y=None,
2985
2989
  ax=None,
2986
- kind: Union[str, list] = None, # Specify the kind of plot
2990
+ kind: Union[str, list] = 'scatter', # Specify the kind of plot
2987
2991
  verbose=False,
2988
2992
  **kwargs,
2989
2993
  ):
@@ -3006,14 +3010,15 @@ def plotxy(
3006
3010
  """
3007
3011
  # Check for valid plot kind
3008
3012
  # Default arguments for various plot types
3009
- default_settings = fload(
3010
- "/Users/macjianfeng/Dropbox/github/python/py2ls/py2ls/data/usages_sns.json"
3011
- )
3012
- sns_info = pd.DataFrame(
3013
- fload(
3014
- "/Users/macjianfeng/Dropbox/github/python/py2ls/py2ls/data/sns_info.json",
3015
- )
3016
- )
3013
+ from pathlib import Path
3014
+ # Get the current script's directory as a Path object
3015
+ current_directory = Path(__file__).resolve().parent
3016
+
3017
+ if not 'default_settings' in locals():
3018
+ default_settings = fload(current_directory / 'data' / 'usages_sns.json')
3019
+ if not 'sns_info' in locals():
3020
+ sns_info = pd.DataFrame(fload(current_directory / 'data' / 'sns_info.json'))
3021
+
3017
3022
  valid_kinds = list(default_settings.keys())
3018
3023
  print(valid_kinds)
3019
3024
  if kind is not None:
@@ -3022,6 +3027,7 @@ def plotxy(
3022
3027
  kind = [strcmp(i, valid_kinds)[0] for i in kind]
3023
3028
  else:
3024
3029
  verbose = True
3030
+
3025
3031
  if verbose:
3026
3032
  if kind is not None:
3027
3033
  for k in kind:
@@ -3033,7 +3039,7 @@ def plotxy(
3033
3039
  .tolist()[0]
3034
3040
  )
3035
3041
  print()
3036
- usage_str = """plot_xy(data=ranked_genes,
3042
+ usage_str = """plotxy(data=ranked_genes,
3037
3043
  x="log2(fold_change)",
3038
3044
  y="-log10(p-value)",
3039
3045
  palette=get_color(3, cmap="coolwarm"),
@@ -3057,24 +3063,15 @@ def plotxy(
3057
3063
  kws_add_text = v_arg
3058
3064
  kwargs.pop(k_arg, None)
3059
3065
  break
3060
-
3066
+ zorder=0
3061
3067
  for k in kind:
3068
+ zorder+=1
3062
3069
  # indicate 'col' features
3063
3070
  col = kwargs.get("col", None)
3064
- sns_with_col = [
3065
- "catplot",
3066
- "histplot",
3067
- "relplot",
3068
- "lmplot",
3069
- "pairplot",
3070
- "displot",
3071
- "kdeplot",
3072
- ]
3071
+ sns_with_col = ["catplot","histplot","relplot","lmplot","pairplot","displot","kdeplot"]
3073
3072
  if col is not None:
3074
3073
  if not k in sns_with_col:
3075
- print(
3076
- f"tips:\n'{k}' has no 'col' param, you could try with {sns_with_col}"
3077
- )
3074
+ print(f"tips:\n'{k}' has no 'col' param, you could try with {sns_with_col}")
3078
3075
  # (1) return FcetGrid
3079
3076
  if k == "jointplot":
3080
3077
  kws_joint = kwargs.pop("kws_joint", kwargs)
@@ -3093,7 +3090,6 @@ def plotxy(
3093
3090
  # (2) return axis
3094
3091
  if ax is None:
3095
3092
  ax = plt.gca()
3096
-
3097
3093
  if k == "catplot":
3098
3094
  kws_cat = kwargs.pop("kws_cat", kwargs)
3099
3095
  g = catplot(data=data, x=x, y=y, ax=ax, **kws_cat)
@@ -3102,88 +3098,86 @@ def plotxy(
3102
3098
  ax = stdshade(ax=ax, **kwargs)
3103
3099
  elif k == "scatterplot":
3104
3100
  kws_scatter = kwargs.pop("kws_scatter", kwargs)
3101
+ kws_scatter={k: v for k, v in kws_scatter.items() if not k.startswith("kws_")}
3105
3102
  hue = kwargs.pop("hue", None)
3106
- palette = kws_scatter.pop(
3107
- "palette",
3108
- (
3109
- sns.color_palette("tab10", data[hue].nunique())
3110
- if hue is not None
3111
- else sns.color_palette("tab10")
3112
- ),
3113
- )
3103
+ if isinstance(kws_scatter, dict): # Check if kws_scatter is a dictionary
3104
+ kws_scatter.pop("hue", None) # Safely remove 'hue' if it exists
3105
+
3106
+ palette = kws_scatter.pop("palette",get_color(data[hue].nunique()) if hue is not None else None)
3114
3107
  s = kws_scatter.pop("s", 10)
3115
3108
  alpha = kws_scatter.pop("alpha", 0.7)
3116
- ax = sns.scatterplot(
3117
- ax=ax,
3118
- data=data,
3119
- x=x,
3120
- y=y,
3121
- hue=hue,
3122
- palette=palette,
3123
- s=s,
3124
- alpha=alpha,
3125
- **kws_scatter,
3126
- )
3109
+ ax = sns.scatterplot(ax=ax,data=data,x=x,y=y,hue=hue,palette=palette,s=s,alpha=alpha,zorder=zorder,**kws_scatter)
3127
3110
  elif k == "histplot":
3128
3111
  kws_hist = kwargs.pop("kws_hist", kwargs)
3129
- ax = sns.histplot(data=data, x=x, ax=ax, **kws_hist)
3112
+ kws_hist={k: v for k, v in kws_hist.items() if not k.startswith("kws_")}
3113
+ ax = sns.histplot(data=data, x=x, ax=ax,zorder=zorder, **kws_hist)
3130
3114
  elif k == "kdeplot":
3131
3115
  kws_kde = kwargs.pop("kws_kde", kwargs)
3132
- ax = sns.kdeplot(data=data, x=x, ax=ax, **kws_kde)
3116
+ kws_kde={k: v for k, v in kws_kde.items() if not k.startswith("kws_")}
3117
+ ax = sns.kdeplot(data=data, x=x, ax=ax,zorder=zorder, **kws_kde)
3133
3118
  elif k == "ecdfplot":
3134
3119
  kws_ecdf = kwargs.pop("kws_ecdf", kwargs)
3135
- ax = sns.ecdfplot(data=data, x=x, ax=ax, **kws_ecdf)
3120
+ kws_ecdf={k: v for k, v in kws_ecdf.items() if not k.startswith("kws_")}
3121
+ ax = sns.ecdfplot(data=data, x=x, ax=ax,zorder=zorder, **kws_ecdf)
3136
3122
  elif k == "rugplot":
3137
3123
  kws_rug = kwargs.pop("kws_rug", kwargs)
3138
- print(kws_rug)
3139
- ax = sns.rugplot(data=data, x=x, ax=ax, **kws_rug)
3124
+ kws_rug={k: v for k, v in kws_rug.items() if not k.startswith("kws_")}
3125
+ ax = sns.rugplot(data=data, x=x, ax=ax,zorder=zorder, **kws_rug)
3140
3126
  elif k == "stripplot":
3141
3127
  kws_strip = kwargs.pop("kws_strip", kwargs)
3128
+ kws_strip={k: v for k, v in kws_strip.items() if not k.startswith("kws_")}
3142
3129
  dodge = kws_strip.pop("dodge", True)
3143
- ax = sns.stripplot(data=data, x=x, y=y, ax=ax, dodge=dodge, **kws_strip)
3130
+ ax = sns.stripplot(data=data, x=x, y=y, ax=ax,zorder=zorder, dodge=dodge, **kws_strip)
3144
3131
  elif k == "swarmplot":
3145
3132
  kws_swarm = kwargs.pop("kws_swarm", kwargs)
3146
- ax = sns.swarmplot(data=data, x=x, y=y, ax=ax, **kws_swarm)
3133
+ kws_swarm={k: v for k, v in kws_swarm.items() if not k.startswith("kws_")}
3134
+ ax = sns.swarmplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_swarm)
3147
3135
  elif k == "boxplot":
3148
3136
  kws_box = kwargs.pop("kws_box", kwargs)
3149
- ax = sns.boxplot(data=data, x=x, y=y, ax=ax, **kws_box)
3137
+ kws_box={k: v for k, v in kws_box.items() if not k.startswith("kws_")}
3138
+ ax = sns.boxplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_box)
3150
3139
  elif k == "violinplot":
3151
3140
  kws_violin = kwargs.pop("kws_violin", kwargs)
3152
- ax = sns.violinplot(data=data, x=x, y=y, ax=ax, **kws_violin)
3141
+ kws_violin={k: v for k, v in kws_violin.items() if not k.startswith("kws_")}
3142
+ ax = sns.violinplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_violin)
3153
3143
  elif k == "boxenplot":
3154
3144
  kws_boxen = kwargs.pop("kws_boxen", kwargs)
3155
- ax = sns.boxenplot(data=data, x=x, y=y, ax=ax, **kws_boxen)
3145
+ kws_boxen={k: v for k, v in kws_boxen.items() if not k.startswith("kws_")}
3146
+ ax = sns.boxenplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_boxen)
3156
3147
  elif k == "pointplot":
3157
3148
  kws_point = kwargs.pop("kws_point", kwargs)
3158
- ax = sns.pointplot(data=data, x=x, y=y, ax=ax, **kws_point)
3149
+ kws_point={k: v for k, v in kws_point.items() if not k.startswith("kws_")}
3150
+ ax = sns.pointplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_point)
3159
3151
  elif k == "barplot":
3160
3152
  kws_bar = kwargs.pop("kws_bar", kwargs)
3161
- ax = sns.barplot(data=data, x=x, y=y, ax=ax, **kws_bar)
3153
+ kws_bar={k: v for k, v in kws_bar.items() if not k.startswith("kws_")}
3154
+ ax = sns.barplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_bar)
3162
3155
  elif k == "countplot":
3163
3156
  kws_count = kwargs.pop("kws_count", kwargs)
3157
+ kws_count={k: v for k, v in kws_count.items() if not k.startswith("kws_")}
3164
3158
  if not kws_count.get("hue",None):
3165
3159
  kws_count.pop("palette",None)
3166
- ax = sns.countplot(data=data, x=x,y=y, ax=ax, **kws_count)
3160
+ ax = sns.countplot(data=data, x=x,y=y, ax=ax,zorder=zorder, **kws_count)
3167
3161
  elif k == "regplot":
3168
3162
  kws_reg = kwargs.pop("kws_reg", kwargs)
3169
- ax = sns.regplot(data=data, x=x, y=y, ax=ax, **kws_reg)
3163
+ kws_reg={k: v for k, v in kws_reg.items() if not k.startswith("kws_")}
3164
+ ax = sns.regplot(data=data, x=x, y=y, ax=ax,zorder=zorder, **kws_reg)
3170
3165
  elif k == "residplot":
3171
3166
  kws_resid = kwargs.pop("kws_resid", kwargs)
3172
- ax = sns.residplot(data=data, x=x, y=y, lowess=True, ax=ax, **kws_resid)
3167
+ kws_resid={k: v for k, v in kws_resid.items() if not k.startswith("kws_")}
3168
+ ax = sns.residplot(data=data, x=x, y=y, lowess=True,zorder=zorder, ax=ax, **kws_resid)
3173
3169
  elif k == "lineplot":
3174
3170
  kws_line = kwargs.pop("kws_line", kwargs)
3175
- ax = sns.lineplot(ax=ax, data=data, x=x, y=y, **kws_line)
3171
+ kws_line={k: v for k, v in kws_line.items() if not k.startswith("kws_")}
3172
+ ax = sns.lineplot(ax=ax, data=data, x=x, y=y,zorder=zorder, **kws_line)
3176
3173
 
3177
3174
  figsets(ax=ax, **kws_figsets)
3178
- print(kws_add_text)
3179
- add_text(ax=ax, **kws_add_text) if kws_add_text else None
3180
- print(k, " ⤵ ")
3181
- print(default_settings[k])
3182
- print(
3183
- "=>\t",
3184
- sns_info[sns_info["Functions"].str.contains(k)].iloc[:, -1].tolist()[0],
3185
- )
3186
- print()
3175
+ if kws_add_text:
3176
+ add_text(ax=ax, **kws_add_text) if kws_add_text else None
3177
+ if run_once_within(60):
3178
+ print(f"\n{k}⤵ ")
3179
+ print(default_settings[k])
3180
+ # print("=>\t",sns_info[sns_info["Functions"].str.contains(k)].iloc[:, -1].tolist()[0],"\n")
3187
3181
  if "g" in locals():
3188
3182
  if ax is not None:
3189
3183
  return g, ax
@@ -3202,7 +3196,7 @@ def volcano(
3202
3196
  top_genes=[5, 5], # [down-regulated, up-regulated]
3203
3197
  thr_x=np.log2(1.5), # default: 0.585
3204
3198
  thr_y=-np.log10(0.05),
3205
- sort_xy="x", #'y'
3199
+ sort_xy="x", #'y', 'xy'
3206
3200
  colors=("#00BFFF", "#9d9a9a", "#FF3030"),
3207
3201
  s=20,
3208
3202
  fill=True, # plot filled scatter
@@ -3835,4 +3829,275 @@ def venn(
3835
3829
  patch.set_alpha(alpha)
3836
3830
  if 'none' in edgecolor or 0 in linewidth:
3837
3831
  patch.set_edgecolor("none")
3832
+ return ax
3833
+
3834
+ #! subplots, support automatic extend new axis
3835
+ def subplot(rows:int=2,
3836
+ cols:int=2,
3837
+ figsize:Union[tuple,list]=[8, 8],
3838
+ sharex=False,
3839
+ sharey=False,
3840
+ **kwargs):
3841
+ """
3842
+ nexttile = subplot(
3843
+ 8,
3844
+ 2,
3845
+ figsize=(8, 9),
3846
+ sharey=True,
3847
+ sharex=True,
3848
+ )
3849
+
3850
+ for i in range(8):
3851
+ ax = nexttile()
3852
+ x = np.linspace(0, 10, 100) + i
3853
+ ax.plot(x, np.sin(x + i) + i, label=f"Plot {i + 1}")
3854
+ ax.legend()
3855
+ ax.set_title(f"Tile {i + 1}")
3856
+ ax.set_ylabel(f"Tile {i + 1}")
3857
+ ax.set_xlabel(f"Tile {i + 1}")
3858
+ """
3859
+ from matplotlib.gridspec import GridSpec
3860
+ if run_once_within():
3861
+ print(f"usage:\n\tnexttile = subplot(2, 2, figsize=(5, 5), sharex=True, sharey=True)\n\tax = nexttile()")
3862
+ fig = plt.figure(figsize=figsize)
3863
+ grid_spec = GridSpec(rows, cols, figure=fig)
3864
+ occupied = set()
3865
+ row_first_axes = [None] * rows # Track the first axis in each row (for sharey)
3866
+ col_first_axes = [None] * cols # Track the first axis in each column (for sharex)
3867
+
3868
+ def expand_ax():
3869
+ nonlocal rows, grid_spec
3870
+ rows += 1 # Expands by adding a row
3871
+ grid_spec = GridSpec(rows, cols, figure=fig)
3872
+ def nexttile(rowspan=1, colspan=1, **kwargs):
3873
+ nonlocal rows, cols, occupied, grid_spec
3874
+ for row in range(rows):
3875
+ for col in range(cols):
3876
+ if all(
3877
+ (row + r, col + c) not in occupied
3878
+ for r in range(rowspan)
3879
+ for c in range(colspan)
3880
+ ):
3881
+ break
3882
+ else:
3883
+ continue
3884
+ break
3885
+ else:
3886
+ expand_ax()
3887
+ return nexttile(rowspan=rowspan, colspan=colspan, **kwargs)
3888
+
3889
+ sharex_ax,sharey_ax = None, None
3890
+
3891
+ if sharex:
3892
+ sharex_ax = col_first_axes[col]
3893
+
3894
+ if sharey:
3895
+ sharey_ax = row_first_axes[row]
3896
+ ax = fig.add_subplot(
3897
+ grid_spec[row : row + rowspan, col : col + colspan],
3898
+ sharex=sharex_ax,
3899
+ sharey=sharey_ax,
3900
+ **kwargs
3901
+ )
3902
+ if row_first_axes[row] is None:
3903
+ row_first_axes[row] = ax
3904
+ if col_first_axes[col] is None:
3905
+ col_first_axes[col] = ax
3906
+ for r in range(row, row + rowspan):
3907
+ for c in range(col, col + colspan):
3908
+ occupied.add((r, c))
3909
+
3910
+ return ax
3911
+
3912
+ return nexttile
3913
+
3914
+
3915
+ #! radar chart
3916
+ def radar(
3917
+ data: pd.DataFrame,
3918
+ ylim=(0,100),
3919
+ color=get_color(5),
3920
+ fontsize=10,
3921
+ fontcolor='k',
3922
+ size=6,
3923
+ linewidth=1,
3924
+ linestyle="-",
3925
+ alpha=0.5,
3926
+ marker="o",
3927
+ edgecolor='none',
3928
+ edge_linewidth=0,
3929
+ bg_color="0.8",
3930
+ bg_alpha=None,
3931
+ grid_interval_ratio=0.2,
3932
+ title="Radar Chart",
3933
+ cmap=None,
3934
+ legend_loc="upper right",
3935
+ legend_fontsize=10,
3936
+ grid_color="gray",
3937
+ grid_alpha=0.5,
3938
+ grid_linestyle="--",grid_linewidth=0.5,
3939
+ circular: bool = False,
3940
+ tick_fontsize=None,
3941
+ tick_fontcolor="0.65",
3942
+ tick_loc = None,# label position
3943
+ turning = None,
3944
+ ax=None,
3945
+ sp=2,
3946
+ **kwargs
3947
+ ):
3948
+ """
3949
+ Example DATA:
3950
+ df = pd.DataFrame(
3951
+ data=[
3952
+ [80, 80, 80, 80, 80, 80, 80],
3953
+ [90, 20, 95, 95, 30, 30, 80],
3954
+ [60, 90, 20, 20, 100, 90, 50],
3955
+ ],
3956
+ index=["Hero", "Warrior", "Wizard"],
3957
+ columns=["HP", "MP", "ATK", "DEF", "SP.ATK", "SP.DEF", "SPD"])
3958
+
3959
+ Parameters:
3960
+ - data (pd.DataFrame): The data to plot. Each column corresponds to a variable, and each row represents a data point.
3961
+ - ylim (tuple): The limits of the radial axis (y-axis). Default is (0, 100).
3962
+ - color: The color(s) for the plot. Can be a single color or a list of colors.
3963
+ - fontsize (int): Font size for the angular labels (x-axis).
3964
+ - fontcolor (str): Color for the angular labels.
3965
+ - size (int): The size of the markers for each data point.
3966
+ - linewidth (int): Line width for the plot lines.
3967
+ - linestyle (str): Line style for the plot lines.
3968
+ - alpha (float): The transparency level for the filled area.
3969
+ - marker (str): The marker style for the data points.
3970
+ - edgecolor (str): The color for the marker edges.
3971
+ - edge_linewidth (int): Line width for the marker edges.
3972
+ - bg_color (str): Background color for the radar chart.
3973
+ - grid_interval_ratio (float): Determines the intervals for the grid lines as a fraction of the y-limit.
3974
+ - title (str): The title of the radar chart.
3975
+ - cmap (str): The colormap to use if `color` is a list.
3976
+ - legend_loc (str): The location of the legend.
3977
+ - legend_fontsize (int): Font size for the legend.
3978
+ - grid_color (str): Color for the grid lines.
3979
+ - grid_alpha (float): Transparency of the grid lines.
3980
+ - grid_linestyle (str): Style of the grid lines.
3981
+ - grid_linewidth (int): Line width of the grid lines.
3982
+ - circular (bool): If True, use circular grid lines. If False, use spider-style grid lines (straight lines).
3983
+ - tick_fontsize (int): Font size for the radial (y-axis) labels.
3984
+ - tick_fontcolor (str): Font color for the radial (y-axis) labels.
3985
+ - tick_loc (float or None): The location of the radial tick labels (between 0 and 1). If None, it is automatically calculated.
3986
+ - turning (float or None): Rotation of the radar chart. If None, it is not applied.
3987
+ - ax (matplotlib.axes.Axes or None): The axis on which to plot the radar chart. If None, a new axis will be created.
3988
+ - sp (int): Padding for the ticks from the plot area.
3989
+ - **kwargs: Additional arguments for customization.
3990
+ """
3991
+ if circular:
3992
+ from matplotlib.colors import to_rgba
3993
+ kws_figsets = {}
3994
+ for k_arg, v_arg in kwargs.items():
3995
+ if "figset" in k_arg:
3996
+ kws_figsets = v_arg
3997
+ kwargs.pop(k_arg, None)
3998
+ break
3999
+ categories = list(data.columns)
4000
+ num_vars = len(categories)
4001
+
4002
+ # Set up angle for each category on radar chart
4003
+ angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
4004
+ angles += angles[:1] # Complete the loop to ensure straight-line connections
4005
+
4006
+ # If no axis is provided, create a new one
4007
+ if ax is None:
4008
+ fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
4009
+
4010
+ # bg_color
4011
+ if bg_alpha is None:
4012
+ bg_alpha=alpha
4013
+ ax.set_facecolor(to_rgba(bg_color,alpha=bg_alpha)) if circular else ax.set_facecolor('none')
4014
+ # Set up the radar chart with straight-line connections
4015
+ ax.set_theta_offset(np.pi / 2)
4016
+ ax.set_theta_direction(-1)
4017
+
4018
+ # Draw one axis per variable and add labels
4019
+ ax.set_xticks(angles[:-1])
4020
+ ax.set_xticklabels(categories)
4021
+
4022
+ # Set y-axis limits and grid intervals
4023
+ vmin, vmax = ylim
4024
+ if circular:
4025
+ #* cicular style
4026
+ ax.yaxis.set_ticks(np.arange(vmin, vmax+1, vmax * grid_interval_ratio))
4027
+ ax.grid(axis='both',
4028
+ color=grid_color,
4029
+ linestyle=grid_linestyle,
4030
+ alpha=grid_alpha,
4031
+ linewidth=grid_linewidth,
4032
+ dash_capstyle='round',
4033
+ dash_joinstyle='round',
4034
+ )
4035
+ ax.spines["polar"].set_color(grid_color)
4036
+ ax.spines["polar"].set_linewidth(grid_linewidth)
4037
+ ax.spines["polar"].set_linestyle('-')
4038
+ ax.spines["polar"].set_alpha(grid_alpha)
4039
+ ax.spines["polar"].set_capstyle('round')
4040
+ ax.spines["polar"].set_joinstyle('round')
4041
+
4042
+ else:
4043
+ #* spider style: spider-style grid (straight lines, not circles)
4044
+ # Create the spider-style grid (straight lines, not circles)
4045
+ for i in range(1, int(vmax * grid_interval_ratio) + 1):
4046
+ ax.plot(
4047
+ angles + [angles[0]], # Closing the loop
4048
+ [i * vmax * grid_interval_ratio] * (num_vars+1) + [i * vmax * grid_interval_ratio],
4049
+ color=grid_color, linestyle=grid_linestyle, alpha=grid_alpha,linewidth=grid_linewidth
4050
+ )
4051
+ # set bg_color
4052
+ ax.fill(angles, [vmax]*(data.shape[1]+1), color=bg_color, alpha=bg_alpha)
4053
+ ax.yaxis.grid(False)
4054
+ # Move radial labels away from plotted line
4055
+ if tick_loc is None:
4056
+ tick_loc = np.mean([angles[0],angles[1]])/(2*np.pi)*360 if circular else 0
4057
+
4058
+ ax.set_rlabel_position(tick_loc)
4059
+ ax.set_theta_offset(turning) if turning is not None else None
4060
+ ax.tick_params(axis='x', labelsize=fontsize, colors=fontcolor) # Optional: for angular labels
4061
+ tick_fontsize = fontsize-2 if fontsize is None else tick_fontsize
4062
+ ax.tick_params(axis='y', labelsize=tick_fontsize, colors=tick_fontcolor) # For radial labels
4063
+ if not circular:
4064
+ ax.spines['polar'].set_visible(False)
4065
+ ax.tick_params(axis='x', pad=sp) # move spines outward
4066
+ ax.tick_params(axis='y', pad=sp) # move spines outward
4067
+ # colors
4068
+ colors = get_color(data.shape[0]) if cmap is None else plt.get_cmap(cmap)(np.linspace(0, 1, data.shape[0]))
4069
+ # Plot each row with straight lines
4070
+ for i, (index, row) in enumerate(data.iterrows()):
4071
+ values = row.tolist()
4072
+ values += values[:1] # Close the loop
4073
+ ax.plot(
4074
+ angles,
4075
+ values,
4076
+ color=colors[i],
4077
+ linewidth=linewidth,
4078
+ linestyle=linestyle,
4079
+ label=index,
4080
+ clip_on=False
4081
+ )
4082
+ ax.fill(angles, values, color=colors[i], alpha=alpha)
4083
+
4084
+ ax.set_ylim(ylim)
4085
+ # Add markers for each data point
4086
+ for i, row in enumerate(data.values):
4087
+ ax.plot(
4088
+ angles,
4089
+ list(row) + [row[0]], # Close the loop for markers
4090
+ color=colors[i],
4091
+ marker=marker,
4092
+ markersize=size,
4093
+ markeredgecolor=edgecolor,
4094
+ markeredgewidth = edge_linewidth, zorder=10,clip_on=False
4095
+ )
4096
+ # ax.tick_params(axis='y', labelleft=False, left=False)
4097
+ if 'legend' in kws_figsets:
4098
+ figsets(ax=ax, **kws_figsets)
4099
+ else:
4100
+
4101
+ figsets(ax=ax,legend=dict(loc=legend_loc,fontsize=legend_fontsize,
4102
+ bbox_to_anchor=[1.1,1.4],ncols=2),**kws_figsets)
3838
4103
  return ax