|
22 | 22 | "Data Release: [Data Preview 1](https://dp1.lsst.io)\\\n", |
23 | 23 | "Container Size: Large\\\n", |
24 | 24 | "LSST Science Pipelines version: r29.2.0\\\n", |
25 | | - "Last verified to run: 2025-12-30\\\n", |
| 25 | + "Last verified to run: 2026-02-03\\\n", |
26 | 26 | "Repository: [github.com/lsst/tutorial-notebooks](https://github.com/lsst/tutorial-notebooks)\\\n", |
27 | 27 | "DOI: [10.11578/rubin/dc.20250909.20](https://doi.org/10.11578/rubin/dc.20250909.20)" |
28 | 28 | ] |
|
85 | 85 | "* `<f>_psfFluxNdata` : The number of associated `diaSources`.\n", |
86 | 86 | "* `<f>_psfFluxErrMean` : Mean of the `diaSource` PSF flux errors.\n", |
87 | 87 | "* `<f>_psfFluxMin`, `Max` : Minimum and maximum `diaSource` PSF flux.\n", |
88 | | - "* `<f>_psfFluxMean`, `MeanErr` : Weighted mean of `diaSource` PSF flux, and its standard error.\n", |
| 88 | + "* `<f>_psfFluxMean`, `MeanErr` : Inverse variance weighted mean of `diaSource` PSF flux, and its standard error.\n", |
89 | 89 | "* `<f>_psfFluxSigma` : Standard deviation of the distribution of `<f>_psfFlux`.\n", |
90 | 90 | "* `<f>_psfFluxMAD` : Median absolute deviation (MAD) of `diaSource` PSF flux. Does not include scale factor for comparison to sigma.\n", |
91 | 91 | "* `<f>_psfFluxChi2` : $\\chi^2$ statistic for the scatter of `<f>_psfFlux` around `<f>_psfFluxMean`.\n", |
|
339 | 339 | "However, because the timeseries features in the `DiaObject` table are calculated using the detection (unforced) flux measurements from the `DiaSource` table, this tutorial also uses them.\n", |
340 | 340 | "The key difference to be aware of is that the `DiaSource` table only contains flux measurements for visits in which the `diaObject` was detected with SNR$\\geq5$ in the difference image, whereas the `ForcedSourceOnDiaObject` table contains forced flux measurements for *all* visits.\n", |
341 | 341 | "\n", |
342 | | - "Define a query to retrieve the $g$ and $r$-band difference-image (`psfFlux`) and direct-image (`scienceFlux`) measurements, the flux errors, and the exposure time midpoint Modified Julian Date (`midpointMjdTai`) from the `DiaSource` table." |
| 342 | + "Define a query to retrieve the $g$- and $r$-band difference-image (`psfFlux`) and direct-image (`scienceFlux`) measurements, the flux errors, and the exposure time midpoint Modified Julian Date (`midpointMjdTai`) from the `DiaSource` table." |
343 | 343 | ] |
344 | 344 | }, |
345 | 345 | { |
|
592 | 592 | "Note that this feature uses the *unweighted* average flux ($\\bar{f}$) in its calculation:\n", |
593 | 593 | "$\\sigma_f = \\sqrt{\\frac{\\sum{(f - \\bar{f})^2}}{N-1}}$.\n", |
594 | 594 | "\n", |
595 | | - "The `<f>_psfFluxMAD`, the median absolute deviation (MAD), is the median value of an array composed of the absolute values of the differences between the `psfFlux` and the median value of the `psfFlux`, for a given filter.\n", |
| 595 | + "The `<f>_psfFluxMAD`, the median absolute deviation (MAD), is the median value of an array composed of the absolute values of the differences between the `psfFlux` and the median value of the `psfFlux`, for a given filter, where the median flux is `<f>_psfFluxPercentile50` (see Section 3.8).\n", |
596 | 596 | "\n", |
597 | 597 | "Recalculate the `<f>_psfFluxSigma` and `MAD` columns using the `DiaSource` table, and confirm they match the `DiaObject` table." |
598 | 598 | ] |
|
625 | 625 | "id": "8ba9a97d-2df3-48c4-92af-ebf95b8444ea", |
626 | 626 | "metadata": {}, |
627 | 627 | "source": [ |
628 | | - "Show the minimum, maximum, mean, sigma, and MAD values as lines on the lightcurve and the flux distribution.\n", |
629 | | - "\n", |
630 | | - "Define the labels, linewidths, and linestyles of the lines to represent the statistical features, then create the plot." |
| 628 | + "Show the minimum and maximum, mean and sigma, and median and MAD values as lines on the lightcurve and the flux distribution." |
631 | 629 | ] |
632 | 630 | }, |
633 | 631 | { |
634 | 632 | "cell_type": "code", |
635 | 633 | "execution_count": null, |
636 | | - "id": "e7dc1ab3-1bf5-4acb-a962-2b18735734f8", |
| 634 | + "id": "3484a8a8-bb89-4b0d-b19d-dd97997f12c4", |
637 | 635 | "metadata": {}, |
638 | 636 | "outputs": [], |
639 | 637 | "source": [ |
640 | | - "s_lb = ['min/max', None, 'mean', 'sigma', None, 'MAD', None]\n", |
641 | | - "s_lw = [1, 1, 1, 2, 2, 1, 1]\n", |
642 | | - "s_ls = ['dashed', 'dashed', 'solid', 'dotted', 'dotted', '-.', '-.']\n", |
643 | 638 | "fig, ax = plt.subplots(2, 2, figsize=(8, 5), sharex='col')\n", |
644 | | - "for f, filt in enumerate(use_filters):\n", |
645 | | - " tx = np.where(star_diaSources['band'] == filt)[0]\n", |
646 | | - " ax[f, 0].errorbar(star_diaSources['midpointMjdTai'][tx], star_diaSources['psfFlux'][tx],\n", |
647 | | - " yerr=star_diaSources['psfFluxErr'][tx], fmt=f_sym[filt],\n", |
648 | | - " ms=7, alpha=0.5, mew=0, color=f_col[filt], label=filt + ', star')\n", |
649 | | - " ax[f, 1].hist(star_diaSources['psfFlux'][tx], bins=30,\n", |
650 | | - " alpha=0.5, color=f_col[filt], label=filt)\n", |
651 | | - " s_vl = [star_diaObject[filt + '_psfFluxMin'], star_diaObject[filt + '_psfFluxMax'],\n", |
652 | | - " star_diaObject[filt + '_psfFluxMean'],\n", |
653 | | - " star_diaObject[filt + '_psfFluxMean'] - star_diaObject[filt + '_psfFluxSigma'],\n", |
654 | | - " star_diaObject[filt + '_psfFluxMean'] + star_diaObject[filt + '_psfFluxSigma'],\n", |
655 | | - " star_diaObject[filt + '_psfFluxMean'] - star_diaObject[filt + '_psfFluxMAD'],\n", |
656 | | - " star_diaObject[filt + '_psfFluxMean'] + star_diaObject[filt + '_psfFluxMAD']]\n", |
657 | | - " for s in range(len(s_vl)):\n", |
658 | | - " ax[f, 0].axhline(s_vl[s], lw=s_lw[s], ls=s_ls[s], color='grey', label=s_lb[s])\n", |
659 | | - " ax[f, 1].axvline(s_vl[s], lw=s_lw[s], ls=s_ls[s], color='grey', label=s_lb[s])\n", |
660 | | - " ax[f, 0].set_ylabel('psfFlux [nJy]')\n", |
661 | | - " ax[f, 1].set_ylabel('N(diaSources)')\n", |
662 | | - " ax[f, 1].legend(bbox_to_anchor=(1.7, 1), loc='upper right')\n", |
663 | | - " del tx, s_vl\n", |
| 639 | + "\n", |
| 640 | + "f = 0\n", |
| 641 | + "filt = 'g'\n", |
| 642 | + "s_lw = [1, 1, 1, 1, 1]\n", |
| 643 | + "s_ls = ['solid', 'solid', 'dashed', 'dotted', 'dotted']\n", |
| 644 | + "\n", |
| 645 | + "s_lb = ['min/max', None, 'mean', 'sigma', None]\n", |
| 646 | + "tx = np.where(star_diaSources['band'] == filt)[0]\n", |
| 647 | + "ax[0, 0].errorbar(star_diaSources['midpointMjdTai'][tx], star_diaSources['psfFlux'][tx],\n", |
| 648 | + " yerr=star_diaSources['psfFluxErr'][tx], fmt=f_sym[filt],\n", |
| 649 | + " ms=7, alpha=0.5, mew=0, color=f_col[filt], label=filt + ', star')\n", |
| 650 | + "ax[0, 1].hist(star_diaSources['psfFlux'][tx], bins=30,\n", |
| 651 | + " alpha=0.5, color=f_col[filt], label=filt)\n", |
| 652 | + "s_vl = [star_diaObject[filt + '_psfFluxMin'], star_diaObject[filt + '_psfFluxMax'],\n", |
| 653 | + " star_diaObject[filt + '_psfFluxMean'],\n", |
| 654 | + " star_diaObject[filt + '_psfFluxMean'] - star_diaObject[filt + '_psfFluxSigma'],\n", |
| 655 | + " star_diaObject[filt + '_psfFluxMean'] + star_diaObject[filt + '_psfFluxSigma']]\n", |
| 656 | + "for s in range(len(s_vl)):\n", |
| 657 | + " ax[0, 0].axhline(s_vl[s], lw=s_lw[s], ls=s_ls[s], color='grey', label=s_lb[s])\n", |
| 658 | + " ax[0, 1].axvline(s_vl[s], lw=s_lw[s], ls=s_ls[s], color='grey', label=s_lb[s])\n", |
| 659 | + "ax[0, 1].legend(bbox_to_anchor=(1.7, 1), loc='upper right')\n", |
| 660 | + "ax[0, 0].set_ylabel('psfFlux [nJy]')\n", |
| 661 | + "del tx, s_vl, s_lb\n", |
| 662 | + "\n", |
| 663 | + "s_lb = ['min/max', None, 'median', 'MAD', None]\n", |
| 664 | + "tx = np.where(star_diaSources['band'] == filt)[0]\n", |
| 665 | + "ax[1, 0].errorbar(star_diaSources['midpointMjdTai'][tx], star_diaSources['psfFlux'][tx],\n", |
| 666 | + " yerr=star_diaSources['psfFluxErr'][tx], fmt=f_sym[filt],\n", |
| 667 | + " ms=7, alpha=0.5, mew=0, color=f_col[filt], label=filt + ', star')\n", |
| 668 | + "ax[1, 1].hist(star_diaSources['psfFlux'][tx], bins=30,\n", |
| 669 | + " alpha=0.5, color=f_col[filt], label=filt)\n", |
| 670 | + "s_vl = [star_diaObject[filt + '_psfFluxMin'], star_diaObject[filt + '_psfFluxMax'],\n", |
| 671 | + " star_diaObject[filt + '_psfFluxPercentile50'],\n", |
| 672 | + " star_diaObject[filt + '_psfFluxPercentile50'] - star_diaObject[filt + '_psfFluxMAD'],\n", |
| 673 | + " star_diaObject[filt + '_psfFluxPercentile50'] + star_diaObject[filt + '_psfFluxMAD']]\n", |
| 674 | + "for s in range(len(s_vl)):\n", |
| 675 | + " ax[1, 0].axhline(s_vl[s], lw=s_lw[s], ls=s_ls[s], color='grey', label=s_lb[s])\n", |
| 676 | + " ax[1, 1].axvline(s_vl[s], lw=s_lw[s], ls=s_ls[s], color='grey', label=s_lb[s])\n", |
| 677 | + "ax[1, 0].set_ylabel('psfFlux [nJy]')\n", |
| 678 | + "ax[1, 1].set_ylabel('N(diaSources)')\n", |
| 679 | + "ax[1, 1].legend(bbox_to_anchor=(1.7, 1), loc='upper right')\n", |
| 680 | + "del tx, s_vl\n", |
664 | 681 | "ax[1, 0].set_xlabel('MJD [d]')\n", |
665 | 682 | "ax[1, 1].set_xlabel('psfFlux [nJy]')\n", |
666 | 683 | "plt.suptitle('Statistical features on the lightcurve and flux distribution of a variable star')\n", |
|
674 | 691 | "id": "42e723a1-be50-4aa2-978b-1b76a4985486", |
675 | 692 | "metadata": {}, |
676 | 693 | "source": [ |
677 | | - "> **Figure 2:** The $g$- and $r$-band lightcurves (left) and flux distributions (right) for the variable star, with the minimum and maximum (dashed), mean (solid), and standard deviation (sigma; dotted) of the flux values overplotted as grey lines." |
| 694 | + "> **Figure 2:** The $g$-band lightcurves (left) and flux distributions (right) for the variable star, with the minimum and maximum (solid), mean and standard deviation (top row; dashed and dotted), and median and MAD (bottom row; dashed and dotted) of the flux values overplotted as grey lines. " |
678 | 695 | ] |
679 | 696 | }, |
680 | 697 | { |
|
687 | 704 | "The `<f>_psfFluxChi2` for a given filter is $\\chi^2 = \\sum\\left(\\frac{f - \\bar{f}}{e_f}\\right)^2$,\n", |
688 | 705 | "where $f$ is the `psfFlux`, $\\bar{f}$ is the weighted mean of the `psfFlux`, and $e_f$ is the `psfFluxErr`.\n", |
689 | 706 | "\n", |
690 | | - "Notice that the equation for $\\chi^2$ is quite similar to the one for $\\sigma_f$, except in $\\chi^2$ the flux differences from the mean are weighted by the flux error.\n", |
| 707 | + "Notice that the equation for $\\chi^2$ is quite similar to the one for $\\sigma_f$, except in $\\chi^2$ the flux differences from the mean are weighted by the flux error, whereas $\\sigma_f$ uses a denominator of $N-1$.\n", |
691 | 708 | "\n", |
692 | 709 | "In a sense, the $\\chi^2$ is a measure of whether the flux variability is more than that expected from random variations due to measurement uncertainty.\n", |
693 | 710 | "\n", |
|
966 | 983 | "\n", |
967 | 984 | "The `<f>_psfFluxLinearSlope` and `<f>_psfFluxLinearIntercept` are the result of fitting a linear regression using the `scipy.optimize.lsq_linear` function.\n", |
968 | 985 | "\n", |
969 | | - "The `psfFluxMaxSlope` is the maximum \"instantaneous\" slope, or the maximum ration of the series of time-ordered values of $\\Delta f / \\Delta t$.\n", |
| 986 | + "The `psfFluxMaxSlope` is the maximum \"instantaneous\" slope, or the maximum ratio of the series of time-ordered values of $\\Delta f / \\Delta t$.\n", |
970 | 987 | "\n", |
971 | 988 | "Demonstrate how to derive the linear fit parameters." |
972 | 989 | ] |
973 | 990 | }, |
974 | 991 | { |
975 | 992 | "cell_type": "code", |
976 | 993 | "execution_count": null, |
977 | | - "id": "9b8e89d5-a363-4095-a70b-9d670621946f", |
| 994 | + "id": "b3ea4d9a-5816-4dc9-bd64-31494f790e7b", |
978 | 995 | "metadata": {}, |
979 | 996 | "outputs": [], |
980 | 997 | "source": [ |
981 | 998 | "for f, filt in enumerate(use_filters):\n", |
982 | 999 | " diao_b = float(star_diaObject[filt + '_psfFluxLinearIntercept'])\n", |
983 | 1000 | " diao_m = float(star_diaObject[filt + '_psfFluxLinearSlope'])\n", |
984 | 1001 | " diao_mm = float(star_diaObject[filt + '_psfFluxMaxSlope'])\n", |
985 | | - " tx = np.where(star_diaSources['band'] == filt)[0]\n", |
986 | | - " A = np.array([star_diaSources['midpointMjdTai'][tx]\n", |
987 | | - " / star_diaSources['psfFluxErr'][tx],\n", |
988 | | - " 1.0 / star_diaSources['psfFluxErr'][tx]]).transpose()\n", |
989 | | - " dias_m, dias_b = lsq_linear(A, star_diaSources['psfFlux'][tx]\n", |
990 | | - " / star_diaSources['psfFluxErr'][tx]).x\n", |
| 1002 | + " tx = np.where((star_diaSources['band'] == filt)\n", |
| 1003 | + " & (~np.isnan(star_diaSources['psfFlux']))\n", |
| 1004 | + " & (~np.isnan(star_diaSources['psfFluxErr']))\n", |
| 1005 | + " & (~np.isnan(star_diaSources['midpointMjdTai'])))[0]\n", |
| 1006 | + " fluxes = star_diaSources['psfFlux'][tx]\n", |
| 1007 | + " errors = star_diaSources['psfFluxErr'][tx]\n", |
| 1008 | + " times = star_diaSources['midpointMjdTai'][tx]\n", |
| 1009 | + " A = np.array([times / errors, 1 / errors]).transpose()\n", |
| 1010 | + " dias_m, dias_b = lsq_linear(A, fluxes / errors).x\n", |
991 | 1011 | " sx = np.argsort(star_diaSources['midpointMjdTai'][tx])\n", |
992 | 1012 | " dias_mm = (np.diff(star_diaSources['psfFlux'][tx[sx]])\n", |
993 | 1013 | " / np.diff(star_diaSources['midpointMjdTai'][tx[sx]])).max()\n", |
|
997 | 1017 | " print('Max Slope %10.0f %10.0f ' % (diao_mm, dias_mm))\n", |
998 | 1018 | " if f == 0:\n", |
999 | 1019 | " print(' ')\n", |
1000 | | - " del diao_b, diao_m, diao_mm, tx, A, dias_b, dias_m, sx, dias_mm" |
| 1020 | + " del diao_b, diao_m, diao_mm, tx, fluxes, errors, times, A, dias_b, dias_m, sx, dias_mm" |
1001 | 1021 | ] |
1002 | 1022 | }, |
1003 | 1023 | { |
1004 | 1024 | "cell_type": "markdown", |
1005 | 1025 | "id": "515aaa7f-6566-4a71-b85c-a73509522900", |
1006 | 1026 | "metadata": {}, |
1007 | 1027 | "source": [ |
1008 | | - "**> Notice:** For the linear fits, the re-derived parameters from the `DiaSource` table are not an exact match to the parameters stored in the `DiaObject` table.\n", |
| 1028 | + ">**Notice:** For the linear fits, the re-derived parameters from the `DiaSource` table are not an exact match to the parameters stored in the `DiaObject` table. It is unclear why as the code above uses the same calculation as in the [diaCalculationPlugin](https://github.com/lsst/meas_base/blob/main/python/lsst/meas/base/diaCalculationPlugins.py) for class `LinearFitDiaPsfFlux`.\n", |
1009 | 1029 | "\n", |
1010 | 1030 | "The known variable star is not expected to exhibit a linear slope.\n", |
1011 | 1031 | "Plot the lightcurve and show the best fit line." |
|
1099 | 1119 | "metadata": {}, |
1100 | 1120 | "outputs": [], |
1101 | 1121 | "source": [] |
| 1122 | + }, |
| 1123 | + { |
| 1124 | + "cell_type": "code", |
| 1125 | + "execution_count": null, |
| 1126 | + "id": "22f49979-af1e-4884-9436-e4b44d143e8a", |
| 1127 | + "metadata": {}, |
| 1128 | + "outputs": [], |
| 1129 | + "source": [] |
1102 | 1130 | } |
1103 | 1131 | ], |
1104 | 1132 | "metadata": { |
|
0 commit comments