make_figures.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. import sys
  4. import os
  5. import io
  6. import subprocess
  7. import numpy as np
  8. import pandas as pd
  9. import matplotlib as mpl
  10. import matplotlib.pyplot as plt
  11. import scipy.stats as sps
  12. import scipy.io.wavfile as siowav
  13. import scipy.signal as sps
  14. os.makedirs("../output", exist_ok=True)
  15. ###
  16. # Global plot settings
  17. mpl.rcParams["font.family"] = "TeX Gyre Pagella"
  18. mpl.rcParams["font.size"] = 8
  19. rst_column_width = 3.3
  20. rst_total_width = 7
  21. ###
  22. # Animals
  23. ordered_deaf_pups = ["b3", "b5", "b8"]
  24. deaf_pups = set(ordered_deaf_pups)
  25. ordered_hearing_pups = ["b2", "b4", "b7"]
  26. hearing_pups = set(ordered_hearing_pups)
  27. all_pups = deaf_pups.union(hearing_pups)
  28. bat_birthdates = pd.DataFrame([("b2", "2017/01/26"),
  29. ("b4", "2017/01/30"),
  30. ("b7", "2017/02/08"),
  31. ("b3", "2017/01/29"),
  32. ("b5", "2017/02/02"),
  33. ("b8", "2017/02/08")], columns=["bat", "birthdate"])
  34. bat_birthdates["birthdate"] = pd.to_datetime(bat_birthdates["birthdate"])
  35. bat_birthdates.set_index("bat", inplace=True)
  36. ###
  37. # Group colors and identity markers
  38. group_colors = dict(hearing="#e1be6a", deaf="#40b0a6")
  39. bat_colors = {**{bat: group_colors["deaf"] for bat in deaf_pups},
  40. **{bat: group_colors["hearing"] for bat in hearing_pups}}
  41. bat_colors["b4"] = "#9b803e"
  42. bat_markers = dict(zip(sorted(all_pups), ["P", "o", "*", "s", "X", "D"]))
  43. ###
  44. # Load data for juveniles
  45. pup_sessions = pd.read_csv("../pup_sessions.csv",
  46. index_col=0, parse_dates=["start_time"])
  47. pup_calls = pd.read_csv("../pup_calls.csv",
  48. index_col=[0, 1], parse_dates=["start_time"], na_values=["--"])
  49. # filter out mothers
  50. pup_calls = pup_calls[pup_calls["calling_bat"].isin(all_pups)].reset_index()
  51. # determine ages (in full days) of the calling bats
  52. birth_dates = bat_birthdates.loc[pup_calls["calling_bat"]]["birthdate"].reset_index(drop=True)
  53. ages = pup_calls["start_time"] - birth_dates
  54. pup_calls["calling_bat_age_in_days"] = ages.dt.days
  55. pup_calls["calling_bat_age_in_weeks"] = ages.dt.days // 7
  56. pup_calls["call_rms"] = pup_calls["call_rms"] + 55
  57. pup_calls["calling_bat_deaf"] = pup_calls["calling_bat_deaf"] != 0
  58. ###
  59. # Load data for adults
  60. def single_value(values):
  61. value_set = set(values)
  62. if len(value_set) != 1:
  63. raise ValueError("non-unity set")
  64. return next(iter(value_set))
  65. adult_calls = pd.read_csv("../adult_calls.csv",
  66. parse_dates=["start_time"],
  67. na_values=["--"],
  68. dtype=dict(group="category", session_id="int"),
  69. index_col=["session_id", "call_id"])
  70. adult_calls["recording_day"] = adult_calls["start_time"].dt.date
  71. adult_calls["call_rms"] -= adult_calls["call_rms"].min()
  72. adult_sessions = pd.read_csv("../adult_sessions.csv",
  73. parse_dates=["start_time"],
  74. dtype=dict(group="category"),
  75. index_col=0)
  76. adult_recording_days = adult_sessions[["group"]] \
  77. .groupby(adult_sessions["start_time"].dt.date) \
  78. .agg(single_value)
  79. adult_recording_days.index.name = "recording_day"
  80. ###
  81. # Data that is excluded from the analysis
  82. # 1. Possible echolocation calls (3 ms or shorter)
  83. pup_calls = pup_calls[pup_calls["call_duration"] > 3e-3]
  84. adult_calls = adult_calls[adult_calls["call_duration"] > 3e-3]
  85. # 2. Sessions that are shorter than 20 minutes
  86. session_gaps = pup_sessions.sort_values("start_time")["start_time"].diff()
  87. new_index = session_gaps.index.insert(0, -1)
  88. new_index = new_index[:-1]
  89. session_gaps.index = new_index
  90. session_gaps.drop(-1, inplace=True)
  91. ids_to_drop = session_gaps[session_gaps < pd.Timedelta(20, "m")].index
  92. pup_calls = pup_calls[~pup_calls["session_id"].isin(ids_to_drop)]
  93. pup_sessions = pup_sessions.drop(ids_to_drop)
  94. ###
  95. # Last two recordings with mother for pups that have more than others
  96. # (to ensure the same total recording time for each individual)
  97. sorted_pup_mother_sessions = pup_sessions[pup_sessions["animal2"].str.startswith("m")] \
  98. .sort_values("start_time")
  99. session_counts_with_mothers = sorted_pup_mother_sessions["animal1"].value_counts()
  100. num_sessions_to_drop = session_counts_with_mothers - session_counts_with_mothers.min()
  101. ids_to_drop = set()
  102. for bat, drop_count in num_sessions_to_drop.iteritems():
  103. if drop_count == 0:
  104. continue
  105. this_bat_sessions = sorted_pup_mother_sessions[sorted_pup_mother_sessions["animal1"] == bat]
  106. ids_to_drop = ids_to_drop.union(this_bat_sessions.tail(drop_count).index)
  107. pup_calls = pup_calls[~pup_calls["session_id"].isin(ids_to_drop)]
  108. pup_sessions = pup_sessions.drop(index=ids_to_drop)
  109. ###
  110. # Mark pre-deafening calls and sessions
  111. sorted_pup_mother_sessions = pup_sessions[pup_sessions["animal2"].str.startswith("m")] \
  112. .sort_values("start_time")
  113. pup_calls["before_deafening"] = False
  114. pup_sessions["before_deafening"] = False
  115. for pup, sessions in sorted_pup_mother_sessions.groupby("animal1"):
  116. first_id = sessions.index[0]
  117. pup_sessions.loc[first_id, "before_deafening"] = True
  118. pup_calls.loc[pup_calls["session_id"] == first_id, "before_deafening"] = True
  119. ###
  120. # Compute activity statistics (separately for pups before and after deafening)
  121. calls_per_pup_and_session = pup_calls.reset_index() \
  122. .groupby(["calling_bat", "session_id"])[["calling_bat"]].count() \
  123. / (20*6) # per 10 seconds
  124. calls_per_pup_and_session.columns = ["calls_per_10s"]
  125. calls_per_pup_and_session.reset_index(inplace=True)
  126. session_dates = pup_sessions.loc[calls_per_pup_and_session["session_id"]]["start_time"]
  127. birth_dates = bat_birthdates.loc[calls_per_pup_and_session["calling_bat"]]["birthdate"]
  128. calls_per_pup_and_session["calling_bat_age_in_days"] = \
  129. (session_dates.reset_index(drop=True) - birth_dates.reset_index(drop=True)).dt.days
  130. calls_per_pup_and_session["calling_bat_age_in_weeks"] = \
  131. calls_per_pup_and_session["calling_bat_age_in_days"] // 7
  132. calls_per_pup_and_session = \
  133. pd.merge(calls_per_pup_and_session, pup_sessions[["before_deafening"]],
  134. left_on="session_id", right_index=True,)
  135. calls_per_pup_and_session.loc[:, "calling_bat_deaf"] = \
  136. calls_per_pup_and_session["calling_bat"].isin(deaf_pups)
  137. dfs = []
  138. for before_deafening in [False, True]:
  139. mask = pup_calls["before_deafening"]
  140. if not before_deafening:
  141. mask = ~mask
  142. gr = pup_calls[mask].groupby(["calling_bat", "calling_bat_age_in_weeks"])
  143. gr = gr[["call_duration", "mean_aperiodicity", "f0_mean", "call_rms"]]
  144. params_per_pup_and_week = gr.median()
  145. params_per_pup_and_week["calls_this_week"] = gr.size()
  146. mask = calls_per_pup_and_session["before_deafening"]
  147. if not before_deafening:
  148. mask = ~mask
  149. gr = calls_per_pup_and_session[mask].groupby(["calling_bat", "calling_bat_age_in_weeks"])
  150. params_per_pup_and_week["calls_per_10s"] = gr["calls_per_10s"].mean()
  151. params_per_pup_and_week.reset_index(inplace=True)
  152. params_per_pup_and_week["before_deafening"] = before_deafening
  153. dfs.append(params_per_pup_and_week)
  154. params_per_pup_and_week = pd.concat(dfs)
  155. gr = adult_calls.groupby(["group", "session_id"], observed=True)
  156. calls_per_adult_group_and_session = gr.size().to_frame(name="calls_per_10s").reset_index()
  157. ###
  158. # Quantities of extracted data
  159. print("Total calls by pups: {}\nCalls by deaf pups: {} ({:.1f} %)\nCalls by hearing pups: {} ({:.1f} %)".format(
  160. len(pup_calls),
  161. np.sum(pup_calls["calling_bat_deaf"]),
  162. np.sum(pup_calls["calling_bat_deaf"]) / len(pup_calls) * 100,
  163. np.sum(~pup_calls["calling_bat_deaf"]),
  164. np.sum(~pup_calls["calling_bat_deaf"] / len(pup_calls) * 100)))
  165. print("Total calls by adults: {}\nCalls by deaf adults: {} ({:.1f} %)\nCalls by hearing adults: {} ({:.1f} %)".format(
  166. len(adult_calls),
  167. np.sum(adult_calls["group"] == "deaf"),
  168. np.sum(adult_calls["group"] == "deaf") / len(adult_calls) * 100,
  169. np.sum(adult_calls["group"] == "hearing"),
  170. np.sum(adult_calls["group"] == "hearing") / len(adult_calls) * 100))
  171. ###
  172. # Run the statistical evaluation
  173. pup_calls.to_csv("../output/pup_calls_for_model.csv", index=False)
  174. calls_per_pup_and_session.to_csv("../output/pup_activity_for_model.csv", index=False)
  175. subprocess.run(["Rscript", "analysis.R"])
  176. ###
  177. # Load the p-value table generated by R
  178. pup_ps = pd.read_csv("../output/pup_model_results.csv", index_col="parameter")
  179. ###
  180. # Development trajectory of call parameters
  181. def plot_trajectories(ax, values, parameter, scale):
  182. line_artists = {}
  183. for bat, df in values.groupby("calling_bat"):
  184. bat_color, bat_marker = bat_colors[bat], bat_markers[bat]
  185. line_artist, = ax.plot(df["calling_bat_age_in_weeks"] + 1, df[parameter] * scale,
  186. c=bat_color, linewidth=0.8)
  187. ax.plot(df["calling_bat_age_in_weeks"] + 1, df[parameter] * scale,
  188. c=bat_color, marker=bat_marker, markersize=5, clip_on=False,
  189. markerfacecolor="none", linestyle="")
  190. line_artists[bat] = line_artist
  191. return line_artists
  192. def plot_histograms(ax, values, parameter, scale, y_range,
  193. p_values=None):
  194. data = {}
  195. if "group" in values:
  196. for group, hearing in [("deaf", False), ("hearing", True)]:
  197. data[hearing] = (values.loc[values["group"] == group, parameter] * scale).values
  198. else:
  199. for hearing, df in values.groupby(values["calling_bat"].isin(hearing_pups)):
  200. data[hearing] = (df[parameter] * scale).values
  201. ax.hist([data[True], data[False]], density=True,
  202. range=y_range, bins=15,
  203. color=[group_colors["hearing"], group_colors["deaf"]],
  204. orientation="horizontal", rwidth=0.85)
  205. if p_values is not None:
  206. p = p_values.loc[parameter, "p_group_adjusted"]
  207. if p < 0.05:
  208. ax.annotate("*", (0.5, 0.6),
  209. xycoords="axes fraction",
  210. ha="center")
  211. def plot_individual_boxplots(ax, values, parameter, scale,
  212. annotate=False, p_values=None):
  213. data = []
  214. bat_groups = []
  215. bats = []
  216. for group, individuals in [("deaf", ordered_deaf_pups),
  217. ("hearing", ordered_hearing_pups)]:
  218. for bat in individuals:
  219. data.append(scale * values.loc[values["calling_bat"] == bat, parameter].dropna().values)
  220. bats.append(bat)
  221. bat_groups.append(group)
  222. artists = ax.boxplot(data, vert=True, manage_ticks=False, showfliers=False,
  223. widths=0.2, positions=np.arange(len(data)) * 0.3,
  224. patch_artist=True,
  225. boxprops=dict(linewidth=0.5),
  226. whiskerprops=dict(linewidth=0.5),
  227. medianprops=dict(linewidth=1, color="#8c1010"),
  228. capprops=dict(linewidth=0.5))
  229. for artist, bat in zip(artists["boxes"], bats):
  230. artist.set_facecolor(bat_colors[bat])
  231. if annotate:
  232. for i, (bat_group, bat) in enumerate(zip(bat_groups, bats)):
  233. xy_top = artists["whiskers"][2 * i + 1].get_xydata()[1]
  234. ax.plot(xy_top[0], xy_top[1] + 10, marker=bat_markers[bat],
  235. c=bat_colors[bat], markerfacecolor="none",
  236. markersize=3, markeredgewidth=0.5)
  237. if p_values is not None:
  238. p = p_values.loc[parameter, "p_group_adjusted"]
  239. if p < 0.05:
  240. ax.annotate("*", (0.5, 0.9),
  241. xycoords="axes fraction",
  242. ha="center")
  243. # In[35]:
  244. def plot_pooled_boxplots(ax, values, parameter, scale):
  245. data = []
  246. for group in ["deaf", "hearing"]:
  247. data.append(scale * values.loc[values["group"] == group, parameter].dropna().values)
  248. artists = ax.boxplot(data, vert=True, manage_ticks=False, showfliers=False,
  249. widths=0.75, positions=np.arange(len(data)),
  250. patch_artist=True,
  251. boxprops=dict(linewidth=0.5),
  252. whiskerprops=dict(linewidth=0.5),
  253. medianprops=dict(linewidth=1, color="#8c1010"),
  254. capprops=dict(linewidth=0.5))
  255. for artist, group in zip(artists["boxes"], ["deaf", "hearing"]):
  256. artist.set_facecolor(group_colors[group])
  257. call_parameters = [("calls_per_10s", 1, "Vocal activity\n[calls/10 s]", (0, 100), 20),
  258. ("call_rms", 1, "Amplitude\n[dB]", (0, 54), 12),
  259. ("call_duration", 1e3, "Duration\n[ms]", (0, 80), 20),
  260. ("f0_mean", 1e-3, "Fundamental\nfrequency [kHz]", (0, 40), 10),
  261. ("mean_aperiodicity", 1, "Aperiodicity\n[1]", (0, 1), 0.25)]
  262. fig_width = rst_total_width
  263. fig_height = 0.7 * fig_width
  264. left_margin, right_margin = 0.75, 0.1
  265. bottom_margin, top_margin = 0.45, 0.45
  266. spacing = 0.2
  267. row_height = (fig_height - bottom_margin - top_margin - (len(call_parameters) - 1) * spacing) / len(call_parameters)
  268. pup_boxplot_width = 0.5
  269. extra_adult_spacing = 0.1
  270. adult_boxplot_width = 0.2
  271. trajectory_width = (fig_width - left_margin - right_margin
  272. - 2 * pup_boxplot_width - adult_boxplot_width
  273. - 3 * spacing - 2 * extra_adult_spacing)
  274. fig = plt.figure(figsize=(fig_width, fig_height))
  275. roman_numerals = ["I", "II", "III", "IV", "V"]
  276. for i, (parameter, scale, y_label, y_range, y_tick_interval) in enumerate(reversed(call_parameters)):
  277. early_ax = fig.add_axes([left_margin / fig_width,
  278. (bottom_margin + i * (row_height + spacing)) / fig_height,
  279. pup_boxplot_width / fig_width,
  280. row_height / fig_height])
  281. late_ax = fig.add_axes([(left_margin + pup_boxplot_width + spacing) / fig_width,
  282. (bottom_margin + i * (row_height + spacing)) / fig_height,
  283. pup_boxplot_width / fig_width,
  284. row_height / fig_height])
  285. adult_ax = fig.add_axes([(left_margin + 2 * pup_boxplot_width
  286. + 2 * spacing + extra_adult_spacing) / fig_width,
  287. (bottom_margin + i * (row_height + spacing)) / fig_height,
  288. adult_boxplot_width / fig_width,
  289. row_height / fig_height])
  290. trajectory_ax = fig.add_axes([(left_margin + 2 * pup_boxplot_width
  291. + adult_boxplot_width + 3 * spacing
  292. + 2 * extra_adult_spacing) / fig_width,
  293. (bottom_margin + i * (row_height + spacing)) / fig_height,
  294. trajectory_width / fig_width,
  295. row_height / fig_height])
  296. overall_ax = fig.add_axes([left_margin / fig_width,
  297. (bottom_margin + i * (row_height + spacing)) / fig_height,
  298. (fig_width - left_margin - right_margin) / fig_width,
  299. row_height / fig_height], frameon=False)
  300. overall_ax.set_xticks([])
  301. overall_ax.set_yticks([])
  302. if parameter == "calls_per_10s":
  303. df = calls_per_pup_and_session
  304. else:
  305. df = pup_calls
  306. plot_individual_boxplots(early_ax,
  307. df.query("before_deafening"),
  308. parameter, scale,
  309. annotate=(i == len(call_parameters) - 1),
  310. p_values=None)
  311. plot_individual_boxplots(late_ax,
  312. df.query("not before_deafening"),
  313. parameter, scale,
  314. annotate=(i == len(call_parameters) - 1),
  315. p_values=pup_ps)
  316. if parameter == "calls_per_10s":
  317. df = calls_per_adult_group_and_session
  318. else:
  319. df = adult_calls
  320. plot_pooled_boxplots(adult_ax, df, parameter, scale)
  321. adult_ax.set_xlim(-0.75, 1.75)
  322. plot_trajectories(trajectory_ax,
  323. params_per_pup_and_week.query("not before_deafening "
  324. "and calling_bat_age_in_weeks <= 24"),
  325. parameter, scale)
  326. trajectory_ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(4))
  327. trajectory_ax.set_xlim(2, 25)
  328. trajectory_ax.spines["left"].set_position(("outward", 3))
  329. if i == 0:
  330. trajectory_ax.set_xlabel("Week of age")
  331. trajectory_ax.xaxis.set_major_formatter(
  332. mpl.ticker.FuncFormatter(lambda x, pos: "{}th".format(int(x))))
  333. else:
  334. for ax in [early_ax, late_ax, trajectory_ax]:
  335. ax.set_xticklabels([])
  336. if i == len(call_parameters) - 1:
  337. early_ax.set_title("a)\n< 2 weeks\n", fontsize=8)
  338. late_ax.set_title("b)\n2–25 weeks\n", fontsize=8)
  339. adult_ax.set_title("c)\n2 years\n", fontsize=8)
  340. trajectory_ax.set_title("d)\nDevelopment trajectory of median parameter values"
  341. "\nat 2–25 weeks of age", fontsize=8)
  342. early_ax.annotate("hearing",
  343. xy=(0, 1), xycoords="axes fraction",
  344. xytext=(5, -10), textcoords="offset points",
  345. verticalalignment="top", horizontalalignment="left",
  346. color=group_colors["hearing"])
  347. early_ax.annotate("deafened",
  348. xy=(0, 1), xycoords="axes fraction",
  349. xytext=(5, 0), textcoords="offset points",
  350. verticalalignment="top", horizontalalignment="left",
  351. color=group_colors["deaf"])
  352. for ax in [early_ax, late_ax, adult_ax, trajectory_ax]:
  353. if ax is adult_ax and parameter == "calls_per_10s":
  354. ax.set_ylim(0, 400)
  355. ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(100))
  356. else:
  357. ax.set_ylim(y_range[0], y_range[1])
  358. ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(y_tick_interval))
  359. for spine in ["right", "top"]:
  360. ax.spines[spine].set_visible(False)
  361. ax.spines["bottom"].set_position(("outward", 3))
  362. for ax in [late_ax, adult_ax, trajectory_ax]:
  363. if (ax is not adult_ax and ax is not trajectory_ax) or parameter != "calls_per_10s":
  364. ax.set_yticklabels([])
  365. for ax in [early_ax, late_ax, adult_ax]:
  366. ax.set_xticks([])
  367. ax.spines["bottom"].set_visible(False)
  368. early_ax.set_ylabel(y_label)
  369. early_ax.yaxis.set_label_coords(-0.32 / pup_boxplot_width, 0.5)
  370. overall_ax.set_ylabel(roman_numerals[-i - 1],
  371. rotation="0",
  372. verticalalignment="center",
  373. labelpad=49)
  374. fig.savefig("../output/main_figure.pdf")
  375. ###
  376. # Parameter tables
  377. per_session_parameters = [("call_rms", 1, "Amplitude [dB]"),
  378. ("call_duration", 1e3, "Duration [ms]"),
  379. ("f0_min", 1e-3, "Minimum fundamental frequency [kHz]"),
  380. ("f0_mean", 1e-3, "Mean fundamental frequency [kHz]"),
  381. ("f0_max", 1e-3, "Maximum fundamental frequency [kHz]"),
  382. ("f0_start", 1e-3, "Fundamental frequency at call onset [kHz]"),
  383. ("f0_end", 1e-3, "Fundamental frequency at end of call [kHz]"),
  384. ("f_min", 1e-3, "Minimum frequency [kHz]"),
  385. ("f_max", 1e-3, "Maximum frequency [kHz]"),
  386. ("bandwidth", 1e-3, "Bandwidth [kHz]"),
  387. ("spectral_centroid", 1e-3, "Spectral centroid frequency [kHz]"),
  388. ("mean_aperiodicity", 1, "Aperiodicity [1]")]
  389. adult_calls.loc[:, "bandwidth"] = adult_calls["f_max"] - adult_calls["f_min"]
  390. pup_calls.loc[:, "bandwidth"] = pup_calls["f_max"] - pup_calls["f_min"]
  391. def q25(series):
  392. return series.quantile(0.25)
  393. def q75(series):
  394. return series.quantile(0.75)
  395. adult_groups = adult_calls[[p[0] for p in per_session_parameters] + ["group"]].groupby("group")
  396. adult_summary = adult_groups.agg([q25, "median", q75, "mean", "std"])
  397. pup_groups = pup_calls.loc[~pup_calls["before_deafening"],
  398. [p[0] for p in per_session_parameters] + ["calling_bat_deaf"]].groupby("calling_bat_deaf")
  399. pup_summary = pup_groups.agg([q25, "median", q75, "mean", "std"])
  400. pre_deafening_pup_groups = pup_calls.loc[pup_calls["before_deafening"],
  401. [p[0] for p in per_session_parameters] + ["calling_bat_deaf"]].groupby("calling_bat_deaf")
  402. pre_deafening_pup_summary = pre_deafening_pup_groups.agg([q25, "median", q75, "mean", "std"])
  403. per_session_parameters.insert(0, ("calls_per_10s", 1, "Vocal activity [calls/10 s]"))
  404. adult_activity_summary = calls_per_adult_group_and_session.groupby("group")[["calls_per_10s"]] \
  405. .agg([q25, "median", q75, "mean", "std"])
  406. adult_summary = pd.concat([adult_activity_summary, adult_summary], axis=1)
  407. pup_activity_summary = calls_per_pup_and_session \
  408. .query("not before_deafening") \
  409. .groupby("calling_bat_deaf")[["calls_per_10s"]] \
  410. .agg([q25, "median", q75, "mean", "std"])
  411. pup_summary = pd.concat([pup_activity_summary, pup_summary], axis=1)
  412. pre_deafening_pup_activity_summary = calls_per_pup_and_session \
  413. .query("before_deafening") \
  414. .groupby("calling_bat_deaf")[["calls_per_10s"]] \
  415. .agg([q25, "median", q75, "mean", "std"])
  416. pre_deafening_pup_summary = \
  417. pd.concat([pre_deafening_pup_activity_summary, pre_deafening_pup_summary], axis=1)
  418. pup_summary.rename({False: "hearing", True: "deaf"}, inplace=True)
  419. pre_deafening_pup_summary.rename({False: "hearing", True: "deaf"}, inplace=True)
  420. for group in ["hearing", "deaf"]:
  421. adult_summary.loc[group, ("count", "total")] = \
  422. (adult_calls["group"] == group).sum()
  423. pup_summary.loc[group, ("count", "total")] = \
  424. ((pup_calls["calling_bat_deaf"] == (group == "deaf"))
  425. & (~pup_calls["before_deafening"])).sum()
  426. pre_deafening_pup_summary.loc[group, ("count", "total")] = \
  427. ((pup_calls["calling_bat_deaf"] == (group == "deaf"))
  428. & (pup_calls["before_deafening"])).sum()
  429. def write_parameters_table(summary, p_values):
  430. fout = io.StringIO()
  431. print("<!DOCTYPE html><body><table>", file=fout)
  432. print("<tr><th></th><th colspan='2'>Hearing ({} vocalisations)</th><th colspan='2'>Deaf ({} vocalisations)</th>".format(
  433. int(summary.loc["hearing", ("count", "total")]),
  434. int(summary.loc["deaf", ("count", "total")])), file=fout, sep="")
  435. if p_values is not None:
  436. print("<th colspan='3'>p < 0.05 (ANOVA)</th>", file=fout, sep="")
  437. print("</tr>", file=fout)
  438. print("<tr><th></th>{}".format("<th>Median (1st &amp; 3rd quartile)</th><th>Mean ± std. dev.</th>" * 2), file=fout, end="")
  439. if p_values is not None:
  440. print("<th>Deafened/hearing</th><th>Age</th><th>Interaction</th>", file=fout, end="")
  441. print("</tr>", file=fout)
  442. for parameter_name, scale, parameter_description in per_session_parameters:
  443. print("<tr><th>{}</th>".format(parameter_description), file=fout, end="")
  444. test_data = []
  445. for group in ["hearing", "deaf"]:
  446. print("<td>{:.2f} ({:.2f}–{:.2f})</td>".format(
  447. summary.loc[group, (parameter_name, "median")] * scale,
  448. summary.loc[group, (parameter_name, "q25")] * scale,
  449. summary.loc[group, (parameter_name, "q75")] * scale),
  450. file=fout, end="")
  451. print("<td>{:.2f} ± {:.2f}</td>".format(
  452. summary.loc[group, (parameter_name, "mean")] * scale,
  453. summary.loc[group, (parameter_name, "std")] * scale),
  454. file=fout, end="")
  455. if p_values is not None:
  456. for p_name in ["p_group_adjusted", "p_age_adjusted", "p_interaction_adjusted"]:
  457. p = p_values.loc[parameter_name, p_name]
  458. if p < 0.05:
  459. print("<td>*</td>", file=fout, end="")
  460. else:
  461. print("<td>{:.2f}</td>".format(p), file=fout, end="")
  462. print("</tr>", file=fout)
  463. print("</table></body>", file=fout)
  464. return fout.getvalue()
  465. parameters_table = write_parameters_table(adult_summary, None)
  466. with open("../output/adult_parameters_table.html", "wt") as fout:
  467. print(parameters_table, file=fout, end="")
  468. parameters_table = write_parameters_table(pup_summary, pup_ps)
  469. with open("../output/pup_parameters_table.html", "wt") as fout:
  470. print(parameters_table, file=fout, end="")
  471. parameters_table = write_parameters_table(pre_deafening_pup_summary, None)
  472. with open("../output/pre_deafening_pup_parameters_table.html", "wt") as fout:
  473. print(parameters_table, file=fout, end="")
  474. print("Done, output files written to ../output/.")