首页 > 解决方案 > 将 4 个图/图形排列成一个网格状

问题描述

我创建了 4 个地块。每个看起来像:

在此处输入图像描述

假设我有 4 个我创建的图,Pi = create_subplot(XYZ)它们是类型的matplotlib.figure.Figure,我想将它们排列在 2×2 网格中。由于创建这个情节并不像这里的情节那么简单,我不能简单地使用类似的东西:

# Some example data to display
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)

fig, axs = plt.subplots(2, 2)
axs[0, 0].plot(x, y)
axs[0, 0].set_title('Axis [0, 0]')
axs[0, 1].plot(x, y, 'tab:orange')
axs[0, 1].set_title('Axis [0, 1]')
axs[1, 0].plot(x, -y, 'tab:green')
axs[1, 0].set_title('Axis [1, 0]')
axs[1, 1].plot(x, -y, 'tab:red')
axs[1, 1].set_title('Axis [1, 1]')

得到这样的结果:

在此处输入图像描述

无论如何我可以安排我的子图(P1, P2, P3, P4)来实现类似于上图的效果吗?或者假设您从磁盘上读取了狮子的图像,并且您想将它放在sin(x). 你不能使用axs[0, 0].plot(lion image). 看在上帝的份上,必须有一种方法可以在 Python 中做到这一点

Pi使用下面的函数创建了绘图(注意,有一些函数叫做 like rc.FUNCTIONrc是我在绘图脚本中导入的另一个模块):

def subplots_savitzky(current_field, deltA ):
county = current_field['county'].unique()[0]
ID = current_field['ID'].unique()[0]

X = current_field['doy']
y = current_field[indeks]

#############################################
###
###             Smoothen
###
#############################################


SG_pred_31 = scipy.signal.savgol_filter(y, window_length= 3, polyorder=1)
SG_pred_32 = scipy.signal.savgol_filter(y, window_length= 3, polyorder=2)

SG_pred_51 = scipy.signal.savgol_filter(y, window_length= 5, polyorder=1)
SG_pred_52 = scipy.signal.savgol_filter(y, window_length= 5, polyorder=2)
SG_pred_53 = scipy.signal.savgol_filter(y, window_length= 5, polyorder=3)

SG_pred_71 = scipy.signal.savgol_filter(y, window_length= 7, polyorder=1)
SG_pred_72 = scipy.signal.savgol_filter(y, window_length= 7, polyorder=2)
SG_pred_73 = scipy.signal.savgol_filter(y, window_length= 7, polyorder=3)

SG_pred_91 = scipy.signal.savgol_filter(y, window_length= 9, polyorder=1)
SG_pred_92 = scipy.signal.savgol_filter(y, window_length= 9, polyorder=2)
SG_pred_93 = scipy.signal.savgol_filter(y, window_length= 9, polyorder=3)

#############################################
###
###             find peaks
###
#############################################

SG_max_min_31 = rc.my_peakdetect(y_axis=SG_pred_31, x_axis=X, delta=deltA);
SG_max_31 =  SG_max_min_31[0]; SG_min_31 =  SG_max_min_31[1];
SG_max_31 = rc.separate_x_and_y(m_list = SG_max_31);
SG_min_31 = rc.separate_x_and_y(m_list = SG_min_31);
SG_max_DoYs_series_31 = pd.Series(SG_max_31[0]);
SG_max_series_31 = pd.Series(SG_max_31[1]);
SG_min_DoYs_series_31 = pd.Series(SG_min_31[0]);
SG_min_series_31 = pd.Series(SG_min_31[1]);


SG_max_min_32 = rc.my_peakdetect(y_axis=SG_pred_32, x_axis=X, delta=deltA);
SG_max_32 =  SG_max_min_32[0]; SG_min_32 =  SG_max_min_32[1];
SG_max_32 = rc.separate_x_and_y(m_list = SG_max_32);
SG_min_32 = rc.separate_x_and_y(m_list = SG_min_32);
SG_max_DoYs_series_32 = pd.Series(SG_max_32[0]);
SG_max_series_32 = pd.Series(SG_max_32[1]);
SG_min_DoYs_series_32 = pd.Series(SG_min_32[0]);
SG_min_series_32 = pd.Series(SG_min_32[1]);

############
############ window 5
############

SG_max_min_51 = rc.my_peakdetect(y_axis=SG_pred_51, x_axis=X, delta=deltA);
SG_max_51 =  SG_max_min_51[0]; SG_min_51 =  SG_max_min_51[1];
SG_max_51 = rc.separate_x_and_y(m_list = SG_max_51);
SG_min_51 = rc.separate_x_and_y(m_list = SG_min_51);
SG_max_DoYs_series_51 = pd.Series(SG_max_51[0]);
SG_max_series_51 = pd.Series(SG_max_51[1]);
SG_min_DoYs_series_51 = pd.Series(SG_min_51[0]);
SG_min_series_51 = pd.Series(SG_min_51[1]);

SG_max_min_52 = rc.my_peakdetect(y_axis=SG_pred_52, x_axis=X, delta=deltA);
SG_max_52 =  SG_max_min_52[0]; SG_min_52 =  SG_max_min_52[1];
SG_max_52 = rc.separate_x_and_y(m_list = SG_max_52);
SG_min_52 = rc.separate_x_and_y(m_list = SG_min_52);
SG_max_DoYs_series_52 = pd.Series(SG_max_52[0]);
SG_max_series_52 = pd.Series(SG_max_52[1]);
SG_min_DoYs_series_52 = pd.Series(SG_min_52[0]);
SG_min_series_52 = pd.Series(SG_min_52[1]);

SG_max_min_53 = rc.my_peakdetect(y_axis=SG_pred_53, x_axis=X, delta=deltA);
SG_max_53 =  SG_max_min_53[0]; SG_min_53 =  SG_max_min_53[1];
SG_max_53 = rc.separate_x_and_y(m_list = SG_max_53);
SG_min_53 = rc.separate_x_and_y(m_list = SG_min_53);
SG_max_DoYs_series_53 = pd.Series(SG_max_53[0]);
SG_max_series_53 = pd.Series(SG_max_53[1]);
SG_min_DoYs_series_53 = pd.Series(SG_min_53[0]);
SG_min_series_53 = pd.Series(SG_min_53[1]);

############
############ window 7
############

SG_max_min_71 = rc.my_peakdetect(y_axis=SG_pred_71, x_axis=X, delta=deltA);
SG_max_71 =  SG_max_min_71[0]; SG_min_71 =  SG_max_min_71[1];
SG_max_71 = rc.separate_x_and_y(m_list = SG_max_71);
SG_min_71 = rc.separate_x_and_y(m_list = SG_min_71);
SG_max_DoYs_series_71 = pd.Series(SG_max_71[0]);
SG_max_series_71 = pd.Series(SG_max_71[1]);
SG_min_DoYs_series_71 = pd.Series(SG_min_71[0]);
SG_min_series_71 = pd.Series(SG_min_71[1]);

SG_max_min_72 = rc.my_peakdetect(y_axis=SG_pred_72, x_axis=X, delta=deltA);
SG_max_72 =  SG_max_min_72[0]; SG_min_72 =  SG_max_min_72[1];
SG_max_72 = rc.separate_x_and_y(m_list = SG_max_72);
SG_min_72 = rc.separate_x_and_y(m_list = SG_min_72);
SG_max_DoYs_series_72 = pd.Series(SG_max_72[0]);
SG_max_series_72 = pd.Series(SG_max_72[1]);
SG_min_DoYs_series_72 = pd.Series(SG_min_72[0]);
SG_min_series_72 = pd.Series(SG_min_72[1]);

SG_max_min_73 = rc.my_peakdetect(y_axis=SG_pred_73, x_axis=X, delta=deltA);
SG_max_73 =  SG_max_min_73[0]; SG_min_73 =  SG_max_min_73[1];
SG_max_73 = rc.separate_x_and_y(m_list = SG_max_73);
SG_min_73 = rc.separate_x_and_y(m_list = SG_min_73);
SG_max_DoYs_series_73 = pd.Series(SG_max_73[0]);
SG_max_series_73 = pd.Series(SG_max_73[1]);
SG_min_DoYs_series_73 = pd.Series(SG_min_73[0]);
SG_min_series_73 = pd.Series(SG_min_73[1]);

############
############ window 9
############

SG_max_min_91 = rc.my_peakdetect(y_axis=SG_pred_91, x_axis=X, delta=deltA);
SG_max_91 =  SG_max_min_91[0]; SG_min_91 =  SG_max_min_91[1];
SG_max_91 = rc.separate_x_and_y(m_list = SG_max_91);
SG_min_91 = rc.separate_x_and_y(m_list = SG_min_91);
SG_max_DoYs_series_91 = pd.Series(SG_max_91[0]);
SG_max_series_91 = pd.Series(SG_max_91[1]);
SG_min_DoYs_series_91 = pd.Series(SG_min_91[0]);
SG_min_series_91 = pd.Series(SG_min_91[1]);

SG_max_min_92 = rc.my_peakdetect(y_axis=SG_pred_92, x_axis=X, delta=deltA);
SG_max_92 =  SG_max_min_92[0]; SG_min_92 =  SG_max_min_92[1];
SG_max_92 = rc.separate_x_and_y(m_list = SG_max_92);
SG_min_92 = rc.separate_x_and_y(m_list = SG_min_92);
SG_max_DoYs_series_92 = pd.Series(SG_max_92[0]);
SG_max_series_92 = pd.Series(SG_max_92[1]);
SG_min_DoYs_series_92 = pd.Series(SG_min_92[0]);
SG_min_series_92 = pd.Series(SG_min_92[1]);

SG_max_min_93 = rc.my_peakdetect(y_axis=SG_pred_93, x_axis=X, delta=deltA);
SG_max_93 =  SG_max_min_93[0]; SG_min_93 =  SG_max_min_93[1];
SG_max_93 = rc.separate_x_and_y(m_list = SG_max_93);
SG_min_93 = rc.separate_x_and_y(m_list = SG_min_93);
SG_max_DoYs_series_93 = pd.Series(SG_max_93[0]);
SG_max_series_93 = pd.Series(SG_max_93[1]);
SG_min_DoYs_series_93 = pd.Series(SG_min_93[0]);
SG_min_series_93 = pd.Series(SG_min_93[1]);

########################################################################################################
########################################################################################################

plotting_dic = { "SG 31" : [SG_pred_31, SG_max_DoYs_series_31, SG_max_series_31],
                 "SG 32" : [SG_pred_32, SG_max_DoYs_series_32, SG_max_series_32],

                 "SG 51" : [SG_pred_51, SG_max_DoYs_series_51, SG_max_series_51],
                 "SG 52" : [SG_pred_52, SG_max_DoYs_series_52, SG_max_series_52],
                 "SG 53" : [SG_pred_53, SG_max_DoYs_series_53, SG_max_series_53],

                 "SG 71" : [SG_pred_71, SG_max_DoYs_series_71, SG_max_series_71],
                 "SG 72" : [SG_pred_72, SG_max_DoYs_series_72, SG_max_series_72],
                 "SG 73" : [SG_pred_73, SG_max_DoYs_series_73, SG_max_series_73],

                 "SG 91" : [SG_pred_91, SG_max_DoYs_series_91, SG_max_series_91],
                 "SG 92" : [SG_pred_92, SG_max_DoYs_series_92, SG_max_series_92],
                 "SG 93" : [SG_pred_93, SG_max_DoYs_series_93, SG_max_series_93]
}

#############################################
###
###             plot
###
#############################################

plot_title = county + ", " + plant + " (" + ID + ")"
sb.set();

fig, ax = plt.subplots(figsize=(8,6));
ax.scatter(X, y, label="Raw data", s=30);

for co, ite in enumerate(plotting_dic):
    ax.plot(X, plotting_dic[ite][0], label = ite, c = eleven_colors[co])
    ax.scatter(plotting_dic[ite][1], plotting_dic[ite][2], s=100, marker='*', c = eleven_colors[co]);

ax.set_title(plot_title);
ax.set(xlabel='DoY', ylabel=indeks)
ax.legend(loc="best");
return (fig)

标签: pythonmatplotlib

解决方案


我稍微更改了代码并这样做:

def subplots_savitzky_2_yrs_panelsss(crr_fld, idx, deltA, SFYr, ax):

if (not("human_system_start_time" in list(crr_fld.columns))):
    crr_fld = rc.add_human_start_time(crr_fld)

eleven_colors = ["gray", "lightcoral", "red", "peru",
                 "darkorange", "gold", "olive", "green",
                 "blue", "violet", "deepskyblue"]

plant = crr_fld['CropTyp'].unique()[0]
# Take care of names, replace "/" and "," and " " by "_"
plant = plant.replace("/", "_")
plant = plant.replace(",", "_")
plant = plant.replace(" ", "_")
plant = plant.replace("__", "_")

county = crr_fld['county'].unique()[0]
ID = crr_fld['ID'].unique()[0]

y = crr_fld[idx]

#############################################
###
###             Smoothen
###
#############################################

SG_pred_31 = scipy.signal.savgol_filter(y, window_length= 3, polyorder=1)
SG_pred_32 = scipy.signal.savgol_filter(y, window_length= 3, polyorder=2)

SG_pred_51 = scipy.signal.savgol_filter(y, window_length= 5, polyorder=1)
SG_pred_52 = scipy.signal.savgol_filter(y, window_length= 5, polyorder=2)
SG_pred_53 = scipy.signal.savgol_filter(y, window_length= 5, polyorder=3)

SG_pred_71 = scipy.signal.savgol_filter(y, window_length= 7, polyorder=1)
SG_pred_72 = scipy.signal.savgol_filter(y, window_length= 7, polyorder=2)
SG_pred_73 = scipy.signal.savgol_filter(y, window_length= 7, polyorder=3)

SG_pred_91 = scipy.signal.savgol_filter(y, window_length= 9, polyorder=1)
SG_pred_92 = scipy.signal.savgol_filter(y, window_length= 9, polyorder=2)
SG_pred_93 = scipy.signal.savgol_filter(y, window_length= 9, polyorder=3)
#############################################
###
###             find peaks
###
#############################################
X = rc.extract_XValues_of_RegularizedTS_2Yrs(crr_fld, SF_yr = SFYr)

d = {'DoY': X, 'Date': pd.to_datetime(crr_fld.human_system_start_time.values).values}
date_df = pd.DataFrame(data=d)
SG_max_min_31 = rc.my_peakdetect(y_axis=SG_pred_31, x_axis=X, delta=deltA);
SG_max_31 =  SG_max_min_31[0]; SG_min_31 =  SG_max_min_31[1];
SG_max_31 = rc.separate_x_and_y(m_list = SG_max_31);
SG_min_31 = rc.separate_x_and_y(m_list = SG_min_31);
SG_max_DoYs_series_31 = pd.Series(SG_max_31[0]);
SG_max_series_31 = pd.Series(SG_max_31[1]);
SG_min_DoYs_series_31 = pd.Series(SG_min_31[0]);
SG_min_series_31 = pd.Series(SG_min_31[1]);


SG_max_min_32 = rc.my_peakdetect(y_axis=SG_pred_32, x_axis=X, delta=deltA);
SG_max_32 =  SG_max_min_32[0]; SG_min_32 =  SG_max_min_32[1];
SG_max_32 = rc.separate_x_and_y(m_list = SG_max_32);
SG_min_32 = rc.separate_x_and_y(m_list = SG_min_32);
SG_max_DoYs_series_32 = pd.Series(SG_max_32[0]);
SG_max_series_32 = pd.Series(SG_max_32[1]);
SG_min_DoYs_series_32 = pd.Series(SG_min_32[0]);
SG_min_series_32 = pd.Series(SG_min_32[1]);

############
############ window 5
############

SG_max_min_51 = rc.my_peakdetect(y_axis=SG_pred_51, x_axis=X, delta=deltA);
SG_max_51 =  SG_max_min_51[0]; SG_min_51 =  SG_max_min_51[1];
SG_max_51 = rc.separate_x_and_y(m_list = SG_max_51);
SG_min_51 = rc.separate_x_and_y(m_list = SG_min_51);
SG_max_DoYs_series_51 = pd.Series(SG_max_51[0]);
SG_max_series_51 = pd.Series(SG_max_51[1]);
SG_min_DoYs_series_51 = pd.Series(SG_min_51[0]);
SG_min_series_51 = pd.Series(SG_min_51[1]);

SG_max_min_52 = rc.my_peakdetect(y_axis=SG_pred_52, x_axis=X, delta=deltA);
SG_max_52 =  SG_max_min_52[0]; SG_min_52 =  SG_max_min_52[1];
SG_max_52 = rc.separate_x_and_y(m_list = SG_max_52);
SG_min_52 = rc.separate_x_and_y(m_list = SG_min_52);
SG_max_DoYs_series_52 = pd.Series(SG_max_52[0]);
SG_max_series_52 = pd.Series(SG_max_52[1]);
SG_min_DoYs_series_52 = pd.Series(SG_min_52[0]);
SG_min_series_52 = pd.Series(SG_min_52[1]);

SG_max_min_53 = rc.my_peakdetect(y_axis=SG_pred_53, x_axis=X, delta=deltA);
SG_max_53 =  SG_max_min_53[0]; SG_min_53 =  SG_max_min_53[1];
SG_max_53 = rc.separate_x_and_y(m_list = SG_max_53);
SG_min_53 = rc.separate_x_and_y(m_list = SG_min_53);
SG_max_DoYs_series_53 = pd.Series(SG_max_53[0]);
SG_max_series_53 = pd.Series(SG_max_53[1]);
SG_min_DoYs_series_53 = pd.Series(SG_min_53[0]);
SG_min_series_53 = pd.Series(SG_min_53[1]);

############
############ window 7
############

SG_max_min_71 = rc.my_peakdetect(y_axis=SG_pred_71, x_axis=X, delta=deltA);
SG_max_71 =  SG_max_min_71[0]; SG_min_71 =  SG_max_min_71[1];
SG_max_71 = rc.separate_x_and_y(m_list = SG_max_71);
SG_min_71 = rc.separate_x_and_y(m_list = SG_min_71);
SG_max_DoYs_series_71 = pd.Series(SG_max_71[0]);
SG_max_series_71 = pd.Series(SG_max_71[1]);
SG_min_DoYs_series_71 = pd.Series(SG_min_71[0]);
SG_min_series_71 = pd.Series(SG_min_71[1]);

SG_max_min_72 = rc.my_peakdetect(y_axis=SG_pred_72, x_axis=X, delta=deltA);
SG_max_72 =  SG_max_min_72[0]; SG_min_72 =  SG_max_min_72[1];
SG_max_72 = rc.separate_x_and_y(m_list = SG_max_72);
SG_min_72 = rc.separate_x_and_y(m_list = SG_min_72);
SG_max_DoYs_series_72 = pd.Series(SG_max_72[0]);
SG_max_series_72 = pd.Series(SG_max_72[1]);
SG_min_DoYs_series_72 = pd.Series(SG_min_72[0]);
SG_min_series_72 = pd.Series(SG_min_72[1]);

SG_max_min_73 = rc.my_peakdetect(y_axis=SG_pred_73, x_axis=X, delta=deltA);
SG_max_73 =  SG_max_min_73[0]; SG_min_73 =  SG_max_min_73[1];
SG_max_73 = rc.separate_x_and_y(m_list = SG_max_73);
SG_min_73 = rc.separate_x_and_y(m_list = SG_min_73);
SG_max_DoYs_series_73 = pd.Series(SG_max_73[0]);
SG_max_series_73 = pd.Series(SG_max_73[1]);
SG_min_DoYs_series_73 = pd.Series(SG_min_73[0]);
SG_min_series_73 = pd.Series(SG_min_73[1]);

############
############ window 9
############

SG_max_min_91 = rc.my_peakdetect(y_axis=SG_pred_91, x_axis=X, delta=deltA);
SG_max_91 =  SG_max_min_91[0]; SG_min_91 =  SG_max_min_91[1];
SG_max_91 = rc.separate_x_and_y(m_list = SG_max_91);
SG_min_91 = rc.separate_x_and_y(m_list = SG_min_91);
SG_max_DoYs_series_91 = pd.Series(SG_max_91[0]);
SG_max_series_91 = pd.Series(SG_max_91[1]);
SG_min_DoYs_series_91 = pd.Series(SG_min_91[0]);
SG_min_series_91 = pd.Series(SG_min_91[1]);

SG_max_min_92 = rc.my_peakdetect(y_axis=SG_pred_92, x_axis=X, delta=deltA);
SG_max_92 =  SG_max_min_92[0]; SG_min_92 =  SG_max_min_92[1];
SG_max_92 = rc.separate_x_and_y(m_list = SG_max_92);
SG_min_92 = rc.separate_x_and_y(m_list = SG_min_92);
SG_max_DoYs_series_92 = pd.Series(SG_max_92[0]);
SG_max_series_92 = pd.Series(SG_max_92[1]);
SG_min_DoYs_series_92 = pd.Series(SG_min_92[0]);
SG_min_series_92 = pd.Series(SG_min_92[1]);

SG_max_min_93 = rc.my_peakdetect(y_axis=SG_pred_93, x_axis=X, delta=deltA);
SG_max_93 =  SG_max_min_93[0]; SG_min_93 =  SG_max_min_93[1];
SG_max_93 = rc.separate_x_and_y(m_list = SG_max_93);
SG_min_93 = rc.separate_x_and_y(m_list = SG_min_93);
SG_max_DoYs_series_93 = pd.Series(SG_max_93[0]);
SG_max_series_93 = pd.Series(SG_max_93[1]);
SG_min_DoYs_series_93 = pd.Series(SG_min_93[0]);
SG_min_series_93 = pd.Series(SG_min_93[1]);

########################################################################################################
########################################################################################################

plotting_dic = { "SG 31" : [SG_pred_31, SG_max_DoYs_series_31, SG_max_series_31],
                 "SG 32" : [SG_pred_32, SG_max_DoYs_series_32, SG_max_series_32],

                 "SG 51" : [SG_pred_51, SG_max_DoYs_series_51, SG_max_series_51],
                 "SG 52" : [SG_pred_52, SG_max_DoYs_series_52, SG_max_series_52],
                 "SG 53" : [SG_pred_53, SG_max_DoYs_series_53, SG_max_series_53],

                 "SG 71" : [SG_pred_71, SG_max_DoYs_series_71, SG_max_series_71],
                 "SG 72" : [SG_pred_72, SG_max_DoYs_series_72, SG_max_series_72],
                 "SG 73" : [SG_pred_73, SG_max_DoYs_series_73, SG_max_series_73],

                 "SG 91" : [SG_pred_91, SG_max_DoYs_series_91, SG_max_series_91],
                 "SG 92" : [SG_pred_92, SG_max_DoYs_series_92, SG_max_series_92],
                 "SG 93" : [SG_pred_93, SG_max_DoYs_series_93, SG_max_series_93]
                 }

#############################################
###
###             plot
###
############################################
plot_title = county + ", " + plant + " (" + ID + "), delta = " + str(deltA)
# sb.set();

ax.scatter(date_df.Date, y, label="Raw data", s = 60);

for co, ite in enumerate(plotting_dic):
    lbl = ite + ", Peaks: " + str(len(plotting_dic[ite][2]))
    # ax.plot(X, plotting_dic[ite][0], label = lbl, c = eleven_colors[co])
    ax.plot(date_df.Date, plotting_dic[ite][0], label = lbl, c = eleven_colors[co])

    date_df_specific = date_df[date_df.DoY.isin(plotting_dic[ite][1])]
             # plotting_dic[ite][1]
    ax.scatter(date_df_specific.Date, plotting_dic[ite][2], s=100, marker='*', c = eleven_colors[co]);

ax.set_title(plot_title);
ax.set(ylabel=idx) # xlabel='Time', 
ax.legend(loc="best");

################################################# ##########

fig, axs = plt.subplots(2, 2, figsize=(20,12),
                        sharex='col', sharey='row',
                        gridspec_kw={'hspace': 0.1, 'wspace': .1})

(ax1, ax2), (ax3, ax4) = axs

subplots_savitzky_2_yrs_panelsss(crr_fld, idx = "EVI", deltA = 0.1, SFYr = SF_year, ax = ax1)
subplots_savitzky_2_yrs_panelsss(crr_fld, idx = "EVI", deltA = 0.2, SFYr = SF_year, ax = ax2)
subplots_savitzky_2_yrs_panelsss(crr_fld, idx = "EVI", deltA = 0.3, SFYr = SF_year, ax = ax3)
subplots_savitzky_2_yrs_panelsss(crr_fld, idx = "EVI", deltA = 0.4, SFYr = SF_year, ax = ax4)

fig_name = "/Users/hn/Desktop/" + county + "_" + plant + "_" + str(SF_year) + "_" + str(counter) + '.png'

plt.savefig(fname = fig_name, \
                    dpi=300,
                    bbox_inches='tight')

推荐阅读