-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsn1987a_plots.py
617 lines (525 loc) · 22.8 KB
/
sn1987a_plots.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
"""
Misc. Functions for plotting sn1987a data
Contains the following functions:
set_plot_fonts
set_axis
start_plot
top_year_axis
plot_extras
"""
#----------------------------------------------------------
# Import Common Modules
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sn1987a_time import convert_time
from fancy_plot import fancy_plot
#----------------------------------------------------------
def ratio_err(top,bottom,top_low,top_high,bottom_low,bottom_high):
"""
function to derive vectors of errors on ratios
uses simple propagation of errors (partial derivatives)
returns lower and upper errorbars
"""
#-make sure input is numpy arrays-
top = np.array(top).astype(float)
top_low = np.array(top_low)
top_high = np.array(top_high)
bottom = np.array(bottom).astype(float)
bottom_low = np.array(bottom_low)
bottom_high = np.array(bottom_high)
#-calculate errorbars-
top_errlow = np.subtract(top,top_low)
top_errhigh = np.subtract(top_high,top)
bottom_errlow = np.subtract(bottom,bottom_low)
bottom_errhigh = np.subtract(bottom_high,bottom)
#-calculate ratio_low-
ratio_low = top/bottom - ( (top_errlow/bottom)**2.0 + (top/(bottom)**2.0*bottom_errlow)**2.0 )**0.5
# ratio_low = np.divide(top,bottom) - ( np.divide(top_errlow,bottom)**2.0 + (np.divide(top,bottom**2.0)*bottom_errlow)**2.0 )**0.5
#-calculate ratio_high-
ratio_high = ( (top_errhigh/bottom)**2.0 + (top/(bottom)**2.0*bottom_errhigh)**2.0 )**0.5 + top/bottom
# return two vectors, err_low and err_high
return ratio_low,ratio_high
#----------------------------------------------------------
def set_plot_fonts(fontsize=15,fontfamily='serif',fontweight='normal'):
"""Set the font properties for a plot"""
font = {'family':fontfamily,'weight':fontweight,'size':fontsize}
matplotlib.rc('font',**font)
matplotlib.rcParams['text.latex.preamble'] = [r"\boldmath"]
plt.rc('text',usetex=True)
#----------------------------------------------------------
def set_axis(ax,x=None,twin=False,title=None,xlab=None,grid=False,
clean=True):
"""Set up a bottom or top time axis for SN1987A plot"""
# font_size = 10
font_size = 13
# fontangle = 50
fontangle = 40
if (twin == False) and (clean == True):
fontangle = 0
if clean == False:
font_size = 9
if twin == False:
ax0 = ax
ax0.xaxis.grid(grid,which='major')
if x is not None:
ax0.set_xticks(x,minor=False)
else:
ax0 = ax.twiny()
# x_min,x_max = plt.xlim()
ax0.set_xlim(ax.get_xlim())
if x is not None:
ax0.set_xticks(x,minor=False)
if xlab is not None:
ax0.set_xticklabels(xlab)
if title is not None:
ax0.set_xlabel(title)
for tick in ax0.xaxis.get_major_ticks():
if twin == False:
tick.label.set_fontsize(font_size)
tick.label.set_rotation(fontangle)
if twin == True:
tick.label2.set_fontsize(font_size)
tick.label2.set_rotation(fontangle)
return ax0
#----------------------------------------------------------
def start_plot(xtitle='',ytitle='',xmin=None,xmax=None,ymin=None,ymax=None,
ylog=False,clean=True,figsize=None):
"""Initialize a SN1987A plot with a time x-axis on bottom"""
#-initialize main plot-
fig,ax1=plt.subplots()
#-set size of plot-
if figsize != None: fig.set_size_inches(figsize[0],figsize[1])
#-set axis titles-
plt.xlabel(xtitle)
plt.ylabel(ytitle)
#-set bottom axis-
if ylog == True: plt.yscale('log')
if clean is True:
grid = False
else:
grid = True
set_axis(ax1,grid=grid,clean=clean)
if xmin is not None or xmax is not None:
ax1.set_xlim(xmin,xmax)
ax1.set_autoscalex_on(False)
if ymin is not None or ymax is not None:
ax1.set_ylim(ymin,ymax)
ax1.set_autoscaley_on(False)
#-add extra space on bottom-
fig.subplots_adjust(bottom=0.13,top=0.87)
return fig,ax1
#----------------------------------------------------------
def top_year_axis(ax,agemin=4400,agemax=10600):
"""Add a top x-axis with year ticks to a plot with a bottom age axis"""
#--get years in range--
years = [str(yr) for yr in range(convert_time(agemin,get='year')+1,
convert_time(agemax,get='year')+1)]
#--get sn1987a ages associated with the years--
ages = [convert_time(yr+'-01-01',get='age',informat='date')
for yr in years]
ax2 = set_axis(ax,x=ages,twin=True,title='Year',xlab=years)
return ax2
#----------------------------------------------------------
def plot_legend(ax,labels=None,colors=['black'],markers=['o'],
location='upper left',
fontsize='medium',frameon=False,scatterpoints=1,numpoints=1,
markerscale=1.5,mews=None,mecs=None,ncol=1,alphas=None,
mealphas=None,
textcolors=None,textshifts=None):
import numbers
#-convert defaults to lists-
if mews is None or isinstance(mews,numbers.Number):
mews = [mews]*len(labels)
if mecs is None: mecs = [None]*len(labels)
if alphas is None:
alphas = [1.0]*len(labels)
if mealphas is None:
mealphas = alphas
if isinstance(textcolors,str):
textcolors = [textcolors]*len(labels)
if isinstance(textshifts,numbers.Number):
textshifts = [textshifts]*len(labels)
mecs = [matplotlib.colors.colorConverter.to_rgba(mecs[i],alpha = mealphas[i]) for i in xrange(len(labels))]
#-dummy position arrays-
emptyx=[0]
emptyy=emptyx
#-plot dummy points to define labels-
for p in range(len(labels)):
# ax.scatter(emptyx,emptyy,color=colors[p],marker=markers[p],label=labels[p],edgecolor=mecs[p],linewidth=mews[p],alpha=alphas[p])
ax.plot(emptyx,emptyy,linestyle='None',color=colors[p],
marker=markers[p],label=labels[p],markeredgecolor=mecs[p],
markeredgewidth=mews[p],alpha=alphas[p])
#-plot legend-
leg = ax.legend(loc=location,fontsize=fontsize,frameon=frameon,
scatterpoints=scatterpoints,numpoints=numpoints,
markerscale=markerscale,ncol=ncol,handletextpad=0)
#-change text colors-
if textcolors is not None or textshifts is not None:
labs = leg.get_texts()
lc = 0
for l in labs:
if textcolors is not None: l.set_color(textcolors[lc])
if textshifts is not None: l.set_position((textshifts[lc],0))
lc = lc + 1
return leg
#----------------------------------------------------------
def plot_pie_legend(ax,labels=['NW','NE','SE','SW'],
colors=['red','orange','blue','green'],
radius=1.0,sizes=[25,25,25,25],fontsizes=None,
yoffset=0.35,xoffset=0.43,textcolor='white',
pos='topright',rec=None,textcolors=None,linewidth=0):
"""Plot a legend in the form of a pie chart"""
if textcolors is None:
textcolors = [textcolor]*len(labels)
if isinstance(fontsizes,float): fontsizes = int(fontsizes)
if isinstance(fontsizes,int):
fontsizes = [fontsizes]*len(labels)
if len(labels) == 2:
startangle=90
else:
startangle=0
#-create inset axes-
# rec = [x,y,xsize,ysize]
if rec is None: # if rec is provided, then it overrides the pos argument
if pos == 'topright':
rec = [0.7,0.67,0.2,0.2]
elif pos == 'topleft':
rec = [0.1,0.67,0.2,0.2]
elif pos == 'bottomright':
rec = [0.7,0.15,0.2,0.2]
elif pos == 'bottomleft':
rec = [0.1,0.2,0.2,0.2]
else:
rec = [0.7,0.67,0.2,0.2] # top right
pax = plt.axes(rec)
#-plot pie chart figure-
wedges,texts=pax.pie(sizes,labels=labels,
colors=colors,radius=radius,startangle=startangle)
pax.set_aspect('equal') # force a circle
#-set label properties-
yoffset=yoffset
xoffset=xoffset
labely = [yoffset-0.05,yoffset-0.05,-yoffset,-yoffset]
labelx = [xoffset,-xoffset,-xoffset,xoffset]
q = 0
for t in texts:
t.set_horizontalalignment('center')
t.set_verticalalignment('center')
t.set_y(labely[q])
t.set_x(labelx[q])
t.set_color(textcolors[q])
if fontsizes is not None: t.set_fontsize(fontsizes[q])
q = q+1
for w in wedges:
w.set_linewidth(linewidth)
return pax
#----------------------------------------------------------
def plot_extras(names='all',agemin=4000,agemax=11000):
"""
Author: Kari A. Frank
Date: September 22, 2015
Purpose: Plot lines on already open age vs Y plots (where Y is
typically either radius or flux
Input:
names : string
comma separated list of names of which extras to plot.
default = 'all'. options are:
'plait1995'- plots position and width of the ER from
Plait1995 (in this case Y must be radius)
'ng2013' - plots the radio break age day=7600 from the
Ng2013 torus fits to radio images, as vertical line
'sugarman2002' - plots location of the ring from Sugarman 2002
(in this case Y must be radius)
'hotspots' - plots the radius and day of appearance for the
HST hot spots in Sugarman 2002
'larsson2014' - plots the age of transition from radioactive
decay to X-ray heating as inner debris energy source
'chandrabroadbreak' - plot as vertical line the best fit
age of the break point in expansion curve
'plaitwidth' - plot distance from chandrabroadbreak radius
to width of plait ring
Output:
- plots to an already open plot
Usage Notes:
- will not add anything to the plot legend
"""
dummy_y = [0.00000001,1000.] #dummy y values for vertical lines
dummy_sym = '.'
if names == 'all' or 'hotspots' in names:
# hot spot appearances in HST, Sugarman2002 table 1
hotspot_ages = [2933,4283,4337,4337,4440,4440,4440,4725,
4816,4816,4999,4999]
hotspot_radii = [0.56,0.673,0.607,0.702,0.565,0.697,0.707,
0.607,0.535,0.629,0.531,0.713]
fancy_plot(hotspot_ages,hotspot_radii,syms='*',
colors='white')
# legcolors+=['white'] #marker will look like border only
# leglabels+=['HST Hot Spots']
# legmecs+=['black']
# legmews+=[0.5]
# legsyms+=['*']
if names == 'all' or 'larsson2014' in names:
#Larsson2013 -- inner debris (HST) morphology transitioned
# from core to edge-brightened due to
# energy source shifting from radioactive decay to X-ray
# illumination
debris_transition_age = [5500,5500]
debris_transition_radius = dummy_y#plot vertical line
fancy_plot(debris_transition_age,debris_transition_radius,
colors='black',line='--',syms=dummy_sym)
#legcolors+=[None] #marker will look like border only
#leglabels+=['Debris Transition']
#legmecs+=[None]
#legmews+=[0.]
#legsyms+=[None]
if names == 'all' or 'plait1995' in names:
# Width and location of optical ring from UV flash,
# Plait1995
# plot horizontal band
plaitwidth = np.array([0.121/2.0,0.121/2.0])
plait_radii = [0.86,0.86]
plait_ages = [agemin,agemax]
fancy_plot(plait_ages,plait_radii,colors='gray',
syms=dummy_sym,errorband=True,yerror=plaitwidth)
if names == 'all' or 'plaitwidth' in names:
# width of Plait ring, from chandra break point
plaitwidth = np.array([0.121/2.0,0.121/2.0])
plait_ages = [-1.0,20000.0]
chandrabreakradii = [0.73+0.121/2.0,0.73+0.121/2.0]
fancy_plot(plait_ages,chandrabreakradii,colors='purple',
syms=dummy_sym,errorband=True,yerror=plaitwidth)
if names == 'all' or 'sugarman2002' in names:
# Location of ring from Sugarman2002 (table 3)
sugarman_radii = [0.829,0.829]
sugarman_ages = [agemin,agemax]
fancy_plot(plait_ages,plait_radii,colors='gray',
syms=dummy_sym,errorband=True,yerror=plaitwidth)
if names == 'all' or 'ng2013' in names:
#Ng2013 Radio expansion measurements: using the torus
# model found the following all around day 7600:
# - break in expansion (decrease in velocity)
# - break in torus opening angle, from constant ~40deg
# to decreasing
# - break in asymmetry; the morphology was ~40% asymmetric,
# but became steadily more symmetric at the break
# - break in the light curve; the slope flattened,
# deviating from previous exponential growth
radiobreak_radii = dummy_y #plot vertical line
radiobreak_age = [7600,7600]
fancy_plot(radiobreak_age,radiobreak_radii,syms=dummy_sym,
colors='black',line=':')
if names == 'all' or 'chandrabroadbreak' in names:
# plot best fit age of the break in expansion from the
# 300-8000 chandra images
chandrabreak_radii = dummy_y #plot vertical line
chandrabreak_age = [5962.77,5962.77]
fancy_plot(chandrabreak_age,chandrabreak_radii,
syms=dummy_sym,colors='black',line='--')
if (names == 'all' or 'ng2013radii' in names or 'ng2013fluxes'
in names):
ngradiifile = ('/astro/research/kaf33/Dropbox/Research/SN1987A'
'/ng2013_ring_radii.txt')
ng2013_ringfits = pd.read_table(ngradiifile,comment='#',
header=0,names=
['age','flux','fluxerr','majorradius',
'majorradiuserr','minorradius',
'minorradiuserr','asymmetry',
'asymmetryerr','phi','phierr','chi2',
'dof'],index_col=False)
# columns: age(index) flux fluxerr majorradius majorradiuserr minorradius minorradiuserr asymmetry asymmetryerr phi phierr chi2 dof
if names == 'all' or 'ng2013radii' in names:
# plt.plot(ng2013_ringfits['age'],ng2013_ringfits[])
fancy_plot(np.array(ng2013_ringfits['age']),
np.array(ng2013_ringfits['majorradius']),
yerror=np.array(ng2013_ringfits['majorradiuserr']),
colors='black',syms='x')
fancy_plot(np.array(ng2013_ringfits['age']),
np.array(ng2013_ringfits['minorradius']),
yerror=np.array(ng2013_ringfits['minorradiuserr']),
colors='black',syms='x')
if names == 'all' or 'ng2013fluxes' in names:
ngradiifile = '~/Dropbox/Research/SN1987A/ng2013_ring_radii.txt'
ng2013_ringfits = pd.read_table(ngradiifile,comment='#',
header=0,names=
['age','flux','fluxerr','majorradius',
'majorradiuserr','minorradius',
'minorradiuserr','asymmetry',
'asymmetryerr','phi','phierr','chi2',
'dof'],index_col=False)
# columns: age(index) flux fluxerr majorradius majorradiuserr minorradius minorradiuserr asymmetry asymmetryerr phi phierr chi2 dof
# plt.plot(ng2013_ringfits['age'],ng2013_ringfits[])
fancy_plot(np.array(ng2013_ringfits['age']),
np.array(ng2013_ringfits['flux']/3.0),
yerror=np.array(ng2013_ringfits['fluxerr']),
colors='black',syms='x')
#----------------------------------------------------------
def standard_age_plot(df,y,ages='age',gratings=None,simoffsets=None,
agemin=4400,agemax=10600,ymin=0.0,ymax=None,
plotextras=None,clean=True,color='black',
overplot=False,ax=None,symsize=8,ytitle=None,
sym='o',yerrlow=None,yerrhigh=None,errinterval=True,
ylog=False,errorband=False,line='',linewidth=1,
figsize=None,mews=None,mecs=None,alphas=1.0,
mealphas=None,linecolor=None,
**kwargs):
"""
Create a standard plot of age vs Y for SN1987A data
Author: Kari A. Frank
Date: 2015-12-18
Input
df : pd.DataFrame
dataframe containing columns for, at a minimum, observation ages and
y values
ages : string
name of the column in df containing the observation ages
y : string
name of the column in df containing the y values to be plotted
yerrlow, yerrhigh : string
name of the columns containing the upper and lower errors on y
errinterval : bool
if True, then assume errors are the lower and upper endpoints of the
error interval and convertthem to error bars before plotting.
gratings : string
name of the column in df specifying the gratings used for each
observation represented in x (and y). if provided, each grating
type will be plotted with a different symbol
simoffsets : string
name of column in df specifying detector sim offsets for each
observation. if provided, the symbol outlines for these
observations will be gray instead of the usual black
agemin,agemax : numerical
minimum and maximum ages, in days for the x-axis
ymin, ymax : numerical
optionally specify the minimum and/or maximum values for the y-axis
plotextras : string
comma separated list of names for the extras to plot (passed directly
to plot_extras)
clean : bool
if False, will plot x ticks and a vertical line across the plot at
every data point to more easily associate specific observations with
a specific data point
color : string
specify a color for all the points. to plot with more than one color,
call this function again with different y and overplot=True.
overplot : bool
if True, will skip creating a new figure and just plot the
provided x and y on the current figure. must also provide
the ax argument.
ax : axis object
used only if overplot=True, to make sure it overplotted on
the correct axis
sym : string
specify the symbol to use (will be ignored if gratings is given)
symsize : numerical
specify the size of the symbols (default 8 is good for
presentations, use 4 to more clearly see error bars)
ylog : bool
plot the y axix on log scale if True (default=False)
errorband : bool
if True will plot the errors as a shaded band rather than bars
(passed directly to fancy_plot). default=False
line : string
format of the line to connect data points. same as in pyplot.plot
and fancy_plot. default = '' (no line)
linewidth : numeric
width of the line to connect data points. same as in pyplot.plot
and fancy_plot. default = 1
linecolor : string
color of the line to connect data points. same as in pyplot.plot
and fancy_plot. default = None
figsize : 2-element numerical tuple
tuple of the form (5,3) to specify the size of the plot in
inches (x,y)
mews : numerical
passed to fancy_plot to set the markeredge width
mecs : string
passed to fancy_plot to set the markeredge color
alphas : numerical
passed directly to fancy_plot to set the symbol alpha
mealphas : numerical
passed directly to fancy_plot to set the markeredge alpha. default
is to be the same as the marker (face) alpha
Notes
- In future, may add ability to also specify plotting a second
column as y values, optionally with a different color and symbols
- To save the plot to a pdf file, initialize the pdffile before calling
this function, e.g.:
from matplotlib.backends.backend_pdf import PdfPages
pdffile = PdfPages(plotfile)
and close it afterwards:
pdffile.savefig()
plt.close()
pdffile.close()
"""
#----Set up symbols----
if gratings is not None:
df['symbols'] = 'd'
# change according to grating
df.loc[(df[gratings].values=='HETG'),'symbols']='o'
df.loc[(df[gratings].values=='LETG'),'symbols']='*'
#----Set up marker edge colors and widths----
if mews is None:
df['mews'] = 0.5
else:
df['mews'] = mews
if mecs is None:
df['mecs'] = color
else:
df['mecs'] = mecs
if mealphas is None:
mealphas = alphas
# if simoffsets is not None:
# # make sure column is strings
# df[simoffsets] = pd.Series([str(off) for off in df[simoffsets]])
# df['mecs'][df[simoffsets] == '-8.42'] = 'gray'
# mecs = df['mecs'].values
#----Set plot fonts----
set_plot_fonts()
#----Initialize Plot----
if overplot is False:
if ytitle is None:
ytitle = y # label it with the column name
fig,ax1 = start_plot(xtitle = 'SN1987A Age [days]',ytitle=ytitle,
xmin=agemin,xmax=agemax,ymin=ymin,ymax=ymax,
ylog=ylog,clean=clean,figsize=figsize)
else:
ax1 = ax
#----Drop rows with missing y data----
df = df.dropna(subset=[y])
if gratings is not None:
sym = df['symbols'].values
#----Convert Error Intervals to Error Bars----
if errinterval is True:
if yerrlow is not None:
yerrlow = df[y] - df[yerrlow]
if yerrhigh is not None:
yerrhigh = df[yerrhigh] - df[y]
else:
if yerrlow is not None:
yerrlow = df[yerrlow]
if yerrhigh is not None:
yerrhigh = df[yerrhigh]
#----Plot the data----
fancy_plot(ax1,df[ages],df[y],yerror=yerrlow,yerror_high=yerrhigh,
syms=sym,mecs=df['mecs'].values,mews=df['mews'].values,
line=line,linewidth=linewidth,errorband=errorband,
colors=color,sizes=symsize,linealpha=1.0,bandalpha=0.1,
alphas=alphas,ealphas=mealphas,mealphas=mealphas)
#----Plot Extras----
if plotextras is not None:
plot_extras(names=plotextras)
#----Plot Legend----
# if overplotting, then need to plot the legend separately after all
# calls to standard_age_plot
# plot_legend(labels=leglabels,colors=legcolors,markers=legsyms,mecs=legmecs,
# mews=legmews,fontsize='small')
#----Plot Top Year Axis---
if overplot is False:
top_year_axis(ax1,agemin=agemin,agemax=agemax)
#----Return----
if overplot is False:
return fig,ax1
else:
return None