本期分享:
云雨图 (RainCloud)
求点赞

求分享

求喜欢

点击关注上方“翔的学术日记”,选择加"星标"置顶重磅干货,第一时间送达!也可后台留言私信我后续希望更新的内容!




import pandas as pdimport numpy as npnp.random.seed(42)group1 = np.random.normal(5, 1.5, 120)group2 = np.random.normal(7, 2.0, 100)group3 = np.random.exponential(2, 140) + 3# 构造 DataFrame(允许不同长度,自动补 NaN)df = pd.concat([pd.Series(group1, name="Control"),pd.Series(group2, name="Treatment A"),pd.Series(group3, name="Treatment B")], axis=1)print(df.shape) # (140, 3) —— 行数 = 最长数组长度print(df.head())

# 标准化输入数据if isinstance(data, (list, tuple)):features = [np.asarray(d).ravel() for d in data]else:features = [data[col].dropna().values for col in data.columns]n_groups = len(features)positions = np.arange(n_groups) * 0.8 + 1.0
# 创建图形if ax is None:if figsize is not None:fig, ax = plt.subplots(figsize=figsize, dpi=100)else:w = width / 100 if width else 6h = height / 100 if height else 4fig, ax = plt.subplots(figsize=(w, h), dpi=100)else:fig = ax.figurerng = np.random.default_rng(42)
# 存储每组小提琴在Y轴上的最大值(用于显著性线定位)violin_ymax_list = []# 绘制每个组for idx, feat in enumerate(features):if len(feat) == 0:violin_ymax_list.append(0)continuepos = positions[idx]color = colors[idx % len(colors)]current_ymax = feat.max()# 绘制半小提琴(右侧)if show_violin:kde = gaussian_kde(feat)margin = 0.1 * (feat.max() - feat.min()) if feat.max() != feat.min() else 0.1x_range = np.linspace(feat.min() - margin, feat.max() + margin, 200)current_ymax = x_range.max()y_density = kde(x_range)y_norm = y_density / y_density.max()profile = violin_width * y_norm# 填充右侧半小提琴ax.fill_betweenx(x_range, pos, pos + profile,facecolor=color, alpha=0.24, edgecolor=color, linewidth=1.0)violin_ymax_list.append(current_ymax)# 绘制原始点(左侧,带抖动)if show_points:jitter = rng.uniform(-points_jitter, points_jitter, len(feat))scatter_color = mcolors.to_rgba(darken_color(color), alpha=points_alpha)ax.scatter(pos + point_offset + jitter, feat,s=points_size**2, color=scatter_color, marker='o', zorder=3)# 绘制箱线图(居中)if show_box:bp = ax.boxplot([feat], patch_artist=True, widths=0.14, positions=[pos], vert=True)box = bp["boxes"][0]box.set_facecolor('white')box.set_edgecolor('black')box.set_linewidth(1.0)for line in bp["whiskers"] + bp["caps"] + bp["medians"]:line.set_color('black')line.set_linewidth(1.0)
# 显著性标记if significance_pairs is not None:line_height = 0.2delta_h = 0# 若有小提琴数据,用其最大值;否则回退到原始数据最大值if violin_ymax_list and max(violin_ymax_list) > 0:global_violin_ymax = max(violin_ymax_list)else:global_violin_ymax = max([max(f) for f in features if len(f) > 0])for x1, x2, sig_text in significance_pairs:y_top = global_violin_ymax + line_height * 2 + delta_h# 绘制横线: ┌───┐ax.plot([positions[x1], positions[x1], positions[x2], positions[x2]],[y_top - line_height, y_top, y_top, y_top - line_height],lw=1.5, c='black')# 添加文本ax.text((positions[x1] + positions[x2]) / 2, y_top + line_height * 0.2,sig_text, ha='center', va='bottom',color='black', fontsize=12, fontweight='bold')delta_h += 0.5# 设置坐标轴(仅 vertical)ax.set_xticks(positions)ax.set_xticklabels(category_names or [f"Group {i+1}" for i in range(n_groups)])ax.set_xlim(positions[0] - 0.4, positions[-1] + 0.5)# Y 轴范围:从最小值到显著性线顶部y_min = min([min(f) for f in features if len(f) > 0])y_max = (global_violin_ymax + line_height * 2 + delta_h - 0.5 + line_height * 0.2 + 1) \if significance_pairs else (max(violin_ymax_list) if violin_ymax_list else max([max(f) for f in features if len(f) > 0]))ax.set_ylim(y_min - 1, y_max + 0.5)ax.set_ylabel("Value")ax.set_xlabel("")if title:ax.figure.suptitle(title, y=1.06)ax.grid(True, axis='y', linestyle=':', alpha=0.5)fig.tight_layout()return fig
求点赞

求分享

求喜欢



商务合作、代码咨询、论文指导请私信联系!

点“阅读原文”下载文章源代码