def mean_position(path, print_pos=False): """Calculate the mean position in a collection of obsids Args: path (str): path to text file with obsids, new line delimited Keyword Args: print_pos (bool): print the RA, Dec out to stdout (default is False) """ obsids = read_obsids_file(path) df = obsids_from_db(obsids) positions = SkyCoord(df["ra_pointing"], df["dec_pointing"], unit=(u.deg, u.deg)) # The use of circmean is accurate only when delta(dec) is small. For the # GLEAM-X declination strips this is the case. mean_ra = circmean(positions.ra) mean_dec = positions.dec.mean() mean_pos = SkyCoord(mean_ra, mean_dec) if print_pos: print(f"{mean_pos.ra.deg} {mean_pos.dec.deg}") return mean_pos.ra.deg, mean_pos.dec.deg
def test_circmean_against_scipy(): # testing against scipy.stats.circmean function # the data is the same as the test before, but in radians data = np.array( [0.89011792, 1.1693706, 0.6981317, 1.90240888, 0.54105207, 6.24827872]) answer = scipy.stats.circmean(data) assert_equal(np.around(answer, 2), np.around(circmean(data), 2))
def tuning_curve_stats(tuning_curve, **kwargs): """ Calculate statistics about a turning curve STATUS : EXPERIMENTAL Calculates various statistics for a turning curve. 1. Mean vector length of a head direction rate map. The value will range from 0 to 1. 0 means that there are so much dispersion that a mean angle cannot be described. 1 means that all data are concentrated at the same direction. Note that 0 does not necessarily indicate a uniform distribution. Calculation is based on Section 26.4, J.H Zar - Biostatistical Analysis 5th edition, see eq. 26.13, 26.14. Parameters ---------- tuning_curve : np.ma.MaskedArray Smoothed turning curve of firing rate as a function of angle Nx1 array kwargs percentile : float Percentile value for the head direction arc calculation Arc is between two points with values around globalPeak * percentile. Value should be in range [0, 1] Returns ------- tcstat : dict hd_score : float Score for how strongly modulated by angle the cell is hd_mvl : float mean vector length hd_peak_rate : float Peak firing rate [Hz] hd_mean_rate : float Mean firing rate [Hz] hd_peak_direction : float Direction of peak firing rate [degrees] hd_peak_direction_rad : float Direction of peak firing rate hd_mean_direction: float Direction of mean firing rate [degrees] hd_mean_direction_rad: float Direction of mean firing rate hd_stdev : float Circular standard deviation [degrees] halfCwInd : int Indicies of at the start, end of the range defined by percentile (clockwise). halfCcwInd : int Indicies of at the start, end of the range defined by percentile (counter-clockwise). halfCwRad : float Angle of the start, end of the range defined by percentile halfCcwRad : float Angle of the start, end of the range defined by percentile arc_angle_rad : float Angle of the arc defined by percentile arc_angle_rad : float Angle of the arc defined by percentile Notes -------- BNT.+analyses.tcStatistics Copyright (C) 2019 by Simon Ball This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. """ debug = kwargs.get("debug", False) percentile = kwargs.get('percentile', default.hd_percentile) ndim = tuning_curve.ndim if ndim != 1: raise error.DimensionMismatchError( "tuning_curve should be a 1D array. You have provided {} dimensions" .format(ndim)) if not 0 <= percentile <= 1: raise error.ArgumentError( "Keyword 'percentile' should be in the range [0, 1]. You provided {:.2f.}" .format(percentile)) if type(tuning_curve) != np.ma.MaskedArray: tuning_curve = np.ma.masked_invalid(tuning_curve) num_bin = tuning_curve.size bin_width = 2 * np.pi / num_bin hb = bin_width / 2 bin_centres = np.linspace(hb, (2 * np.pi) - hb, num_bin) if debug: print("Num_bin: %d" % num_bin) print("Therefore, bin_width = %.3g deg = %.3g rad" % (np.degrees(bin_width), bin_width)) #### Calculate the simple values tcstat = {} # The average of the values of angles, weighted by the firing rate at those angles mean_dir_radians = cs.circmean(data=bin_centres, weights=tuning_curve) tcstat['hd_mean_direction_rad'] = mean_dir_radians tcstat['hd_mean_direction'] = np.degrees(mean_dir_radians) # The direction in which the highest firing rate occurs peak_dir_index = np.nanargmax(tuning_curve) peak_dir_angle_radians = _index_to_angle(peak_dir_index, bin_width) tcstat['hd_peak_direction_rad'] = peak_dir_angle_radians tcstat['hd_peak_direction'] = np.degrees(peak_dir_angle_radians) # The peak firing rate IN Hz peak_rate_hz = np.nanmax(tuning_curve) tcstat['hd_peak_rate'] = peak_rate_hz # The mean firing rate across all angles IN Hz if tuning_curve.mask.all(): #### Added to cope with numpy bug in nanmean with fully masked array mean_rate_hz = np.nan else: mean_rate_hz = np.nanmean(tuning_curve) tcstat['hd_mean_rate'] = mean_rate_hz #### Calculate the more complex ones: # mvl mvl = np.sum(tuning_curve * np.exp(1j * bin_centres)) mvl = np.abs(mvl) / np.sum(tuning_curve) tcstat['hd_mvl'] = mvl # hd_stdev # Eq. 26.20 from J. H. Zar tcstat['hd_stdev'] = np.sqrt(2 * (1 - mvl)) # Percentile arc half_peak = peak_rate_hz * percentile # Because Python doesn't natively handle circular arrays, reshape such that # the peak rate occurs at the centre of the array - then don't have to worry # about whether the arc goes off one edge of the array or not # Must be careful to keep track of the array to which the indicies point tuning_curve_re = np.zeros_like(tuning_curve) centre_index = int(num_bin / 2) offset = centre_index - peak_dir_index tuning_curve_re = np.roll(tuning_curve, offset) # A positive offset means that the peak angle was in the range [0, pi], and # is now at the central index. Therefore, to get the "proper" index, # subtract offset from index in tuning_curve_re if debug: print("Centre index: %d, value" % centre_index) print("Peak index: %d" % peak_dir_index) print("Offset: %d" % offset) # Clockwise and counter-clockwise edges of arc around peak defined by # percentile. ccw index +1 to account for width of central peak cw_hp_index = np.where(tuning_curve_re >= (half_peak))[0][0] - offset ccw_hp_index = np.where(tuning_curve_re >= (half_peak))[0][-1] - offset + 1 cw_hp_ang = _index_to_angle(cw_hp_index, bin_width) ccw_hp_ang = _index_to_angle(ccw_hp_index, bin_width) arc_angle = ccw_hp_ang - cw_hp_ang if debug: print("CW: %d, %.3g rad" % (cw_hp_index, cw_hp_ang)) print("CCW: %d, %.3g rad" % (ccw_hp_index, ccw_hp_ang)) print("Arc: %.3g rad" % arc_angle) score = 1 - (arc_angle / np.pi) tcstat['halfCwInd'] = cw_hp_index tcstat['halfCcwInd'] = ccw_hp_index tcstat['halfCwRad'] = cw_hp_ang tcstat['halfCcwRad'] = ccw_hp_ang tcstat['arc_angle_rad'] = arc_angle tcstat['arc_angle_deg'] = np.degrees(arc_angle) tcstat['hd_score'] = score return tcstat
def hro(Imap, Qmap, Umap, steps=10, hsize=15, minI=0., mask=0, ksz=1, w=None, convention='Planck', sigmaQQ=None, sigmaUU=None, mcflag=None, nruns=10, errorbar=None, segmap=None, debug=False): #if (convention=='Planck'): # Qmap0=Qmap # Umap0=Umap #else: # Qmap0=Qmap # Umap0=-1.*Umap if np.logical_or(np.logical_and(sigmaQQ is None, sigmaUU is None), mcflag): output0 = hroLITE(Imap, Qmap, Umap, steps=steps, hsize=hsize, minI=minI, mask=mask, ksz=ksz, w=w, convention=convention, segmap=segmap, debug=debug) isteps = output0['csteps'] asteps = output0['asteps'] hros = output0['hros'] shros = output0['s_hros'] zeta = output0['xi'] #roms[0] Zx = output0['Zx'] #roms[1] Zy = output0['Zy'] mphi = output0['meanphi'] #roms[2] s_zeta = output0['s_xi'] #sroms[0] s_Zx = output0['s_Zx'] #sroms[1] s_Zy = output0['s_Zy'] s_mphi = output0['s_meanphi'] #sroms[2] Amap = output0['Amap'] else: assert Qmap.shape == sigmaQQ.shape, "Dimensions of Qmap and sigmaQQ must match" assert Umap.shape == sigmaUU.shape, "Dimensions of Umap and sigmaUU must match" hrosvec = np.zeros([nruns, steps, hsize]) zetavecMC = np.zeros([nruns, steps]) ZxvecMC = np.zeros([nruns, steps]) ZyvecMC = np.zeros([nruns, steps]) mphivecMC = np.zeros([nruns, steps]) mrlMC = np.zeros([nruns, steps]) s_zetavecMC = np.zeros([nruns, steps]) s_ZxvecMC = np.zeros([nruns, steps]) s_ZyvecMC = np.zeros([nruns, steps]) s_mphivecMC = np.zeros([nruns, steps]) s_mrlMC = np.zeros([nruns, steps]) Acube = np.zeros([nruns, Qmap.shape[0], Qmap.shape[1]]) pbar = tqdm(total=nruns) for i in range(0, nruns): QmapR = np.random.normal(loc=Qmap, scale=sigmaQQ) UmapR = np.random.normal(loc=Umap, scale=sigmaUU) hrooutput = hroLITE(Imap, QmapR, UmapR, steps=steps, hsize=hsize, minI=minI, mask=mask, ksz=ksz, w=w, convention=convention, segmap=segmap, debug=False) zetavecMC[i, :] = hrooutput['xi'] ZxvecMC[i, :] = hrooutput['Zx'] ZyvecMC[i, :] = hrooutput['Zy'] mphivecMC[i, :] = hrooutput['meanphi'] mrlMC[i, :] = hrooutput['mrl'] s_zetavecMC[i, :] = hrooutput['s_xi'] s_ZxvecMC[i, :] = hrooutput['s_Zx'] s_ZyvecMC[i, :] = hrooutput['s_Zy'] s_mphivecMC[i, :] = hrooutput['s_meanphi'] hrosvec[i, :, :] = hrooutput['hros'] Acube[i, :, :] = hrooutput['Amap'] pbar.update() pbar.close() zeta = zetavecMC.mean(axis=0) Zx = ZxvecMC.mean(axis=0) Zy = ZyvecMC.mean(axis=0) meanphi = circstats.circmean(mphivecMC, axis=0) mrl = mrlMC.mean(axis=0) Amap = circstats.circmean(Acube, axis=0) if (errorbar == 'MC'): s_zeta = s_zetavecMC.std(axis=0) s_Zx = s_ZxvecMC.std(axis=0) s_Zy = s_ZyvecMC.std(axis=0) s_meanphi = circ.descriptive.mean(s_mphivecMC, axis=0) else: s_zeta = hrooutput[ 's_xi'] #np.max([s_zetavecMC.std(axis=0),hrooutput['s_xi']], axis=0) s_Zx = hrooutput[ 's_Zx'] #np.max([s_prsvecMC.std(axis=0),hrooutput['s_prs']], axis=0) s_Zy = hrooutput['s_Zy'] s_meanphi = hrooutput[ 's_meanphi'] #np.max([circ.descriptive.mean(s_mphivecMC, axis=0),hrooutput['s_meanphi']], axis=0) s_mrl = mrlMC.std(axis=0) csteps = hrooutput['csteps'] asteps = hrooutput['asteps'] Smap = hrooutput['Smap'] outhros = hrosvec.mean(axis=0) s_outhros = hrosvec.std(axis=0) return { 'csteps': csteps, 'xi': zeta, 's_xi': s_zeta, 'Zx': Zx, 's_Zx': s_Zx, 'Zy': Zy, 's_Zy': s_Zy, 'meanphi': meanphi, 's_meanphi': s_meanphi, 'asteps': asteps, 'hros': outhros, 's_hros': s_outhros, 'mrl': mrl, 's_mrl': s_mrl, 'Smap': Smap, 'Amap': Amap }
def test_circmean(): # testing against R CircStats package # Ref[1], page 23 data = np.array([51, 67, 40, 109, 31, 358]) * u.deg answer = 48.63 * u.deg assert_equal(answer, np.around(circmean(data), 2))