1
+ """Lineplot of dissimilarity over time
2
+
3
+ See demo_meg_mne for an example.
4
+ """
1
5
from __future__ import annotations
2
6
from typing import TYPE_CHECKING , Tuple , List , Optional
3
7
import matplotlib .pyplot as plt
6
10
from rsatoolbox .rdm .rdms import RDMs
7
11
from matplotlib .axes ._axes import Axes
8
12
from matplotlib .figure import Figure
13
+ from numpy .typing import NDArray
9
14
10
15
11
16
def plot_timecourse (
12
- rdms_data : RDMs ,
13
- descriptor : str ,
14
- n_t_display :int = 20 , #
15
- fig_width : Optional [int ] = None ,
16
- timecourse_plot_rel_height : Optional [int ] = None ,
17
+ rdms_data : RDMs ,
18
+ descriptor : str ,
19
+ n_t_display :int = 20 ,
20
+ fig_width : Optional [int ] = None ,
21
+ timecourse_plot_rel_height : Optional [int ] = None ,
17
22
time_formatted : Optional [List [str ]] = None ,
18
23
colored_conditions : Optional [list ] = None ,
19
24
plot_individual_dissimilarities : Optional [bool ] = None ,
20
- ) -> Tuple [Figure , List [Axes ]]:
25
+ ) -> Tuple [Figure , List [Axes ]]:
21
26
""" plots the RDM movie for a given descriptor
22
27
23
28
Args:
24
29
rdms_data (rsatoolbox.rdm.RDMs): rdm movie
25
30
descriptor (str): name of the descriptor that created the rdm movie
26
31
n_t_display (int, optional): number of RDM time points to display. Defaults to 20.
27
32
fig_width (int, optional): width of the figure (in inches). Defaults to None.
28
- timecourse_plot_rel_height (int, optional): height of the timecourse plot (relative to the rdm movie row).
29
- time_formatted (List[str], optional): time points formatted as strings.
30
- Defaults to None (i.e., rdms_data.time_descriptors['time'] is considered to be in seconds)
31
- colored_condiitons (list, optional): vector of pattern condition names to dissimilarities according to a categorical model on colored_conditions Defaults to None.
32
- plot_individual_dissimilarities (bool, optional): whether to plot the individual dissimilarities. Defaults to None (i.e., False if colored_conditions is notNone, True otherwise).
33
+ timecourse_plot_rel_height (int, optional): height of the timecourse plot (relative to
34
+ the rdm movie row).
35
+ time_formatted (List[str], optional): time points formatted as strings.
36
+ Defaults to None (i.e., rdms_data.time_descriptors['time'] is considered to
37
+ be in seconds)
38
+ colored_condiitons (list, optional): vector of pattern condition names to dissimilarities
39
+ according to a categorical model on colored_conditions Defaults to None.
40
+ plot_individual_dissimilarities (bool, optional): whether to plot the individual
41
+ dissimilarities. Defaults to None (i.e., False if colored_conditions is not
42
+ None, True otherwise).
33
43
34
44
Returns:
35
45
Tuple[matplotlib.figure.Figure, npt.ArrayLike, collections.defaultdict]:
36
-
46
+
37
47
Tuple of
38
48
- Handle to created figure
39
49
- Subplot axis handles from plt.subplots.
40
50
"""
41
51
# create labels
42
52
time = rdms_data .rdm_descriptors ['time' ]
43
53
unique_time = np .unique (time )
44
- time_formatted = time_formatted or ['%0.0f ms' % ( np .round (x * 1000 ,2 )) for x in unique_time ]
45
-
46
- n_dissimilarity_elements = rdms_data .dissimilarities .shape [1 ]
47
-
54
+ time_formatted = time_formatted or [f' { np .round (x * 1000 ,2 ):0.0f } ms' for x in unique_time ]
55
+
56
+ n_dissimilarity_elements = rdms_data .dissimilarities .shape [1 ]
57
+
48
58
# color mapping from colored conditions
49
- unsquareform = lambda a : a [ np . nonzero ( np . triu ( a , k = 1 ))]
50
- if colored_conditions is not None :
51
- plot_individual_dissimilarities = False if plot_individual_dissimilarities is None else plot_individual_dissimilarities
52
- unsquare_idx = np . triu_indices ( n_dissimilarity_elements , k = 1 )
53
- pairwise_conds = unsquareform (np .array ([[{ c1 , c2 } for c1 in colored_conditions ] for c2 in colored_conditions ] ))
59
+ if colored_conditions is not None :
60
+ if plot_individual_dissimilarities is None :
61
+ plot_individual_dissimilarities = False
62
+ sf_conds = [[{ c1 , c2 } for c1 in colored_conditions ] for c2 in colored_conditions ]
63
+ pairwise_conds = unsquareform (np .array (sf_conds ))
54
64
pairwise_conds_unique = np .unique (pairwise_conds )
55
- cnames = np .unique (colored_conditions )
56
- color_index = {f'{ list (x )[0 ]} vs { list (x )[1 ]} ' if len (list (x ))== 2 else f'{ list (x )[0 ]} vs { list (x )[0 ]} ' : pairwise_conds == x for x in pairwise_conds_unique }
65
+ color_index = {}
66
+ for x in pairwise_conds_unique :
67
+ if len (list (x ))== 2 :
68
+ key = f'{ list (x )[0 ]} vs { list (x )[1 ]} '
69
+ else :
70
+ key = f'{ list (x )[0 ]} vs { list (x )[0 ]} '
71
+ color_index [key ] = pairwise_conds == x
57
72
else :
58
73
color_index = {'' : np .array ([True ]* n_dissimilarity_elements )}
59
74
plot_individual_dissimilarities = True
60
-
75
+
61
76
colors = plt .get_cmap ('turbo' )(np .linspace (0 , 1 , len (color_index )+ 1 ))
62
-
77
+
63
78
# how many rdms to display
64
- t_display_idx = (np .round (np .linspace (0 , len (unique_time )- 1 , min (len (unique_time ), n_t_display )))).astype (int )
79
+ n_times = len (unique_time )
80
+ t_display_idx = (np .round (np .linspace (0 , n_times - 1 , min (n_times , n_t_display )))).astype (int )
65
81
t_display_idx = np .unique (t_display_idx )
66
82
n_t_display = len (t_display_idx )
67
-
83
+
68
84
# auto determine relative sizes of axis
69
85
timecourse_plot_rel_height = timecourse_plot_rel_height or n_t_display // 3
70
- base_size = 40 / n_t_display if fig_width is None else fig_width / n_t_display
71
-
86
+ base_size = 40 / n_t_display if fig_width is None else fig_width / n_t_display
87
+
72
88
# figure layout
73
- fig = plt .figure (constrained_layout = True , figsize = (base_size * n_t_display ,base_size * timecourse_plot_rel_height ))
89
+ fig = plt .figure (
90
+ constrained_layout = True ,
91
+ figsize = (base_size * n_t_display ,base_size * timecourse_plot_rel_height )
92
+ )
74
93
gs = fig .add_gridspec (timecourse_plot_rel_height + 1 , n_t_display )
75
94
tc_ax = fig .add_subplot (gs [:- 1 ,:])
76
95
rdm_axes = [fig .add_subplot (gs [- 1 ,i ]) for i in range (n_t_display )]
77
96
78
- # plot dissimilarity timecourses
79
-
97
+ # plot dissimilarity timecourses
80
98
dissimilarities_mean = np .zeros ((rdms_data .dissimilarities .shape [1 ], len (unique_time )))
81
-
82
99
for i , t in enumerate (unique_time ):
83
100
dissimilarities_mean [:, i ] = np .mean (rdms_data .dissimilarities [t == time , :], axis = 0 )
84
-
85
- def _plot_mean_dissimilarities (labels = False ):
101
+
102
+ def _plot_mean_dissimilarities (labels = False ):
86
103
for i , (pairwise_name , idx ) in enumerate (color_index .items ()):
87
104
mn = np .mean (dissimilarities_mean [idx , :],axis = 0 )
88
- se = np .std (dissimilarities_mean [idx , :],axis = 0 )/ np .sqrt (dissimilarities_mean .shape [0 ]) # se is over dissimilarities, not over subjects
105
+ n = np .sqrt (dissimilarities_mean .shape [0 ])
106
+ # se is over dissimilarities, not over subjects
107
+ se = np .std (dissimilarities_mean [idx , :],axis = 0 )/ n
89
108
tc_ax .fill_between (unique_time , mn - se , mn + se , color = colors [i ], alpha = .3 )
90
- tc_ax .plot (unique_time , mn , color = colors [i ], linewidth = 2 , label = pairwise_name if labels else None )
91
-
109
+ label = pairwise_name if labels else None
110
+ tc_ax .plot (unique_time , mn , color = colors [i ], linewidth = 2 , label = label )
111
+
92
112
def _plot_individual_dissimilarities ():
93
- for i , (pairwise_name , idx ) in enumerate (color_index .items ()):
94
- tc_ax .plot (unique_time , dissimilarities_mean [idx , :].T , color = colors [i ], alpha = max (1 / 255. , 1 / n_dissimilarity_elements ))
95
-
113
+ for i , (_ , idx ) in enumerate (color_index .items ()):
114
+ a = max (1 / 255. , 1 / n_dissimilarity_elements )
115
+ tc_ax .plot (unique_time , dissimilarities_mean [idx , :].T , color = colors [i ], alpha = a )
116
+
96
117
if plot_individual_dissimilarities :
97
118
if colored_conditions is not None :
98
119
_plot_mean_dissimilarities ()
@@ -101,29 +122,38 @@ def _plot_individual_dissimilarities():
101
122
tc_ax .set_ylim (yl )
102
123
else :
103
124
_plot_individual_dissimilarities ()
104
-
125
+
105
126
if colored_conditions is not None :
106
127
_plot_mean_dissimilarities (True )
107
-
128
+
108
129
yl = tc_ax .get_ylim ()
109
130
for t in unique_time [t_display_idx ]:
110
131
tc_ax .plot ([t ,t ], yl , linestyle = ':' , color = 'b' , alpha = 0.3 )
111
132
tc_ax .set_ylabel (f'Dissimilarity\n ({ rdms_data .dissimilarity_measure } )' )
112
133
tc_ax .set_xticks (unique_time )
113
- tc_ax .set_xticklabels ([time_formatted [idx ] if idx in t_display_idx else '' for idx in range (len (unique_time ))])
134
+ tc_ax .set_xticklabels ([
135
+ time_formatted [idx ] if idx in t_display_idx else '' for idx in range (len (unique_time ))
136
+ ])
114
137
dt = np .diff (unique_time [t_display_idx ])[0 ]
115
138
tc_ax .set_xlim (unique_time [t_display_idx [0 ]]- dt / 2 , unique_time [t_display_idx [- 1 ]]+ dt / 2 )
116
139
117
140
tc_ax .legend ()
118
-
141
+
119
142
# display (selected) rdms
120
143
vmax = np .std (rdms_data .dissimilarities ) * 2
121
- for i , (tidx , a ) in enumerate (zip (t_display_idx , rdm_axes )):
122
- a .imshow (np .mean (rdms_data .subset ('time' , unique_time [tidx ]).get_matrices (),axis = 0 ), vmin = 0 , vmax = vmax );
123
- a .set_title ('%0.0f ms' % (np .round (unique_time [tidx ]* 1000 ,2 )))
144
+ for i , (tidx , a ) in enumerate (zip (t_display_idx , rdm_axes )):
145
+ mean_dissim = np .mean (rdms_data .subset ('time' , unique_time [tidx ]).get_matrices (),axis = 0 )
146
+ a .imshow (mean_dissim , vmin = 0 , vmax = vmax )
147
+ a .set_title (f'{ np .round (unique_time [tidx ]* 1000 ,2 ):0.0f} ms' )
124
148
a .set_yticklabels ([])
125
- a .set_yticks ([])
149
+ a .set_yticks ([])
126
150
a .set_xticklabels ([])
127
- a .set_xticks ([])
128
-
151
+ a .set_xticks ([])
152
+
129
153
return fig , [tc_ax ] + rdm_axes
154
+
155
+
156
+ def unsquareform (a : NDArray ) -> NDArray :
157
+ """Helper function; convert squareform to vector
158
+ """
159
+ return a [np .nonzero (np .triu (a , k = 1 ))]
0 commit comments