├── .editorconfig
├── .github
└── workflows
│ ├── README.md
│ ├── pr-close-signal.yaml
│ ├── pr-comment.yaml
│ ├── pr-post-remove-branch.yaml
│ ├── pr-preflight.yaml
│ ├── pr-receive.yaml
│ ├── sandpaper-main.yaml
│ ├── sandpaper-version.txt
│ ├── update-cache.yaml
│ └── update-workflows.yaml
├── .gitignore
├── AUTHORS
├── CITATION
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── config.yaml
├── episodes
├── 01-query.md
├── 02-coords.md
├── 03-transform.md
├── 04-motion.md
├── 05-select.md
├── 06-join.md
├── 07-photo.md
├── 08-plot.md
├── build.sh
├── convert.py
├── data
│ ├── gd1_candidates.hdf5
│ ├── gd1_dataframe.hdf5
│ ├── gd1_df5.hdf5
│ ├── gd1_isochrone.hdf5
│ ├── gd1_merged.hdf5
│ ├── gd1_photo.fits
│ └── gd1_results.fits
└── fig
│ ├── 03-transform_files
│ ├── 03-transform_102_0.png
│ ├── 03-transform_103_0.png
│ ├── 03-transform_104_0.png
│ ├── 03-transform_112_0.png
│ ├── 03-transform_28_0.png
│ ├── 03-transform_29_0.png
│ ├── 03-transform_30_0.png
│ ├── 03-transform_43_0.png
│ ├── 03-transform_45_0.png
│ ├── 03-transform_46_0.png
│ ├── 03-transform_47_0.png
│ ├── 03-transform_68_0.png
│ ├── 03-transform_70_0.png
│ ├── 03-transform_71_0.png
│ ├── 03-transform_78_0.png
│ ├── 03-transform_80_0.png
│ ├── 03-transform_82_0.png
│ ├── 03-transform_84_0.png
│ ├── 03-transform_85_0.png
│ ├── 03-transform_86_0.png
│ ├── 03-transform_87_0.png
│ ├── 03-transform_90_0.png
│ ├── 03-transform_93_0.png
│ ├── 03-transform_94_0.png
│ └── 03-transform_97_0.png
│ ├── 04-motion_files
│ ├── 04-motion_100_0.png
│ ├── 04-motion_102_0.png
│ ├── 04-motion_103_0.png
│ ├── 04-motion_104_0.png
│ ├── 04-motion_110_0.png
│ ├── 04-motion_112_0.png
│ ├── 04-motion_29_0.png
│ ├── 04-motion_30_0.png
│ ├── 04-motion_45_0.png
│ ├── 04-motion_46_0.png
│ ├── 04-motion_47_0.png
│ ├── 04-motion_67_0.png
│ ├── 04-motion_68_0.png
│ ├── 04-motion_69_0.png
│ ├── 04-motion_70_0.png
│ ├── 04-motion_71_0.png
│ ├── 04-motion_78_0.png
│ ├── 04-motion_80_0.png
│ ├── 04-motion_82_0.png
│ ├── 04-motion_84_0.png
│ ├── 04-motion_85_0.png
│ ├── 04-motion_86_0.png
│ ├── 04-motion_87_0.png
│ ├── 04-motion_90_0.png
│ ├── 04-motion_92_0.png
│ ├── 04-motion_93_0.png
│ ├── 04-motion_94_0.png
│ ├── 04-motion_97_0.png
│ └── 04-motion_plot_pm_selection.png
│ ├── 05-select_files
│ ├── 05-select_12_0.png
│ ├── 05-select_13_0.png
│ ├── 05-select_14_0.png
│ ├── 05-select_15_0.png
│ ├── 05-select_16_0.png
│ ├── 05-select_17_0.png
│ ├── 05-select_28_0.png
│ ├── 05-select_29_0.png
│ ├── 05-select_30_0.png
│ ├── 05-select_31_0.png
│ ├── 05-select_56_0.png
│ ├── 05-select_57_0.png
│ ├── 05-select_58_0.png
│ ├── 05-select_62_0.png
│ ├── 05-select_63_0.png
│ ├── 05-select_64_0.png
│ ├── 05-select_65_0.png
│ ├── 05-select_66_0.png
│ ├── 05-select_67_0.png
│ ├── 05-select_70_0.png
│ ├── 05-select_71_0.png
│ ├── 05-select_72_0.png
│ └── 05-select_73_0.png
│ ├── 06-join_files
│ ├── 06-join_10_0.png
│ ├── 06-join_70_0.png
│ ├── 06-join_76_0.png
│ └── 06-join_78_0.png
│ ├── 07-photo_files
│ ├── 07-cmd_lims.png
│ ├── 07-cmd_no_lims.png
│ ├── 07-photo_106_0.png
│ ├── 07-photo_108_0.png
│ ├── 07-photo_114_0.png
│ ├── 07-photo_116_0.png
│ ├── 07-photo_11_0.png
│ ├── 07-photo_12_0.png
│ ├── 07-photo_13_0.png
│ ├── 07-photo_14_0.png
│ ├── 07-photo_41_0.png
│ ├── 07-photo_42_0.png
│ ├── 07-photo_43_0.png
│ ├── 07-photo_44_0.png
│ ├── 07-photo_46_0.png
│ ├── 07-photo_51_0.png
│ ├── 07-photo_52_0.png
│ ├── 07-photo_53_0.png
│ ├── 07-photo_54_0.png
│ ├── 07-photo_61_0.png
│ ├── 07-photo_62_0.png
│ ├── 07-photo_63_0.png
│ ├── 07-photo_64_0.png
│ ├── 07-photo_66_0.png
│ ├── 07-photo_69_0.png
│ ├── 07-photo_70_0.png
│ ├── 07-photo_71_0.png
│ ├── 07-photo_72_0.png
│ ├── 07-photo_90_0.png
│ ├── 07-photo_91_0.png
│ ├── 07-photo_92_0.png
│ ├── 07-photo_93_0.png
│ └── 07-photo_94_0.png
│ ├── 08-plot_files
│ ├── 08-plot_12_0.png
│ ├── 08-plot_13_0.png
│ ├── 08-plot_14_0.png
│ ├── 08-plot_51_0.png
│ ├── 08-plot_52_0.png
│ ├── 08-plot_53_0.png
│ ├── 08-plot_54_0.png
│ ├── 08-plot_58_0.png
│ ├── 08-plot_59_0.png
│ ├── 08-plot_61_0.png
│ ├── 08-plot_62_0.png
│ ├── 08-plot_63_0.png
│ ├── 08-plot_64_0.png
│ ├── 08-plot_67_0.png
│ ├── 08-plot_68_0.png
│ ├── 08-plot_69_0.png
│ ├── 08-plot_70_0.png
│ ├── 08-plot_71_0.png
│ ├── 08-plot_72_0.png
│ ├── 08-plot_73_0.png
│ ├── 08-plot_75_0.png
│ ├── 08-plot_76_0.png
│ ├── 08-plot_78_0.png
│ ├── 08-plot_79_0.png
│ ├── 08-plot_adjusted_size_fig1.png
│ ├── 08-plot_equal_size_fig1.png
│ └── 08-poly_example.png
│ ├── Gaia-HR-diagram.jpeg
│ ├── gd1-1.png
│ ├── gd1-2.png
│ ├── gd1-3.png
│ ├── gd1-4.png
│ ├── gd1-5.png
│ ├── join.odg
│ └── join.png
├── index.md
├── instructors
├── calculating_MIST_isochrone.md
├── instructor-notes.md
├── link-list.md
└── prereqs.md
├── learners
├── discuss.md
├── reference.md
└── setup.md
├── profiles
└── learner-profiles.md
├── site
└── README.md
└── student_download
├── az-paper-twocol.mplstyle
├── data
├── gd1_data.csv
├── gd1_data.hdf
└── gd1_results.fits
├── environment.yml
├── episode_functions.py
├── gd1_isochrone.hdf5
└── test_setup.ipynb
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 |
8 | [*.md]
9 | indent_size = 2
10 | indent_style = space
11 | max_line_length = 100 # Please keep this in sync with bin/lesson_check.py!
12 | trim_trailing_whitespace = false # keep trailing spaces in markdown - 2+ spaces are translated to a hard break ( )
13 |
14 | [*.r]
15 | max_line_length = 80
16 |
17 | [*.py]
18 | indent_size = 4
19 | indent_style = space
20 | max_line_length = 79
21 |
22 | [*.sh]
23 | end_of_line = lf
24 |
25 | [Makefile]
26 | indent_style = tab
27 |
--------------------------------------------------------------------------------
/.github/workflows/README.md:
--------------------------------------------------------------------------------
1 | # Carpentries Workflows
2 |
3 | This directory contains workflows to be used for Lessons using the {sandpaper}
4 | lesson infrastructure. Two of these workflows require R (`sandpaper-main.yaml`
5 | and `pr-receive.yaml`) and the rest are bots to handle pull request management.
6 |
7 | These workflows will likely change as {sandpaper} evolves, so it is important to
8 | keep them up-to-date. To do this in your lesson you can do the following in your
9 | R console:
10 |
11 | ```r
12 | # Install/Update sandpaper
13 | options(repos = c(carpentries = "https://carpentries.r-universe.dev/",
14 | CRAN = "https://cloud.r-project.org"))
15 | install.packages("sandpaper")
16 |
17 | # update the workflows in your lesson
18 | library("sandpaper")
19 | update_github_workflows()
20 | ```
21 |
22 | Inside this folder, you will find a file called `sandpaper-version.txt`, which
23 | will contain a version number for sandpaper. This will be used in the future to
24 | alert you if a workflow update is needed.
25 |
26 | What follows are the descriptions of the workflow files:
27 |
28 | ## Deployment
29 |
30 | ### 01 Build and Deploy (sandpaper-main.yaml)
31 |
32 | This is the main driver that will only act on the main branch of the repository.
33 | This workflow does the following:
34 |
35 | 1. checks out the lesson
36 | 2. provisions the following resources
37 | - R
38 | - pandoc
39 | - lesson infrastructure (stored in a cache)
40 | - lesson dependencies if needed (stored in a cache)
41 | 3. builds the lesson via `sandpaper:::ci_deploy()`
42 |
43 | #### Caching
44 |
45 | This workflow has two caches; one cache is for the lesson infrastructure and
46 | the other is for the the lesson dependencies if the lesson contains rendered
47 | content. These caches are invalidated by new versions of the infrastructure and
48 | the `renv.lock` file, respectively. If there is a problem with the cache,
49 | manual invaliation is necessary. You will need maintain access to the repository
50 | and you can either go to the actions tab and [click on the caches button to find
51 | and invalidate the failing cache](https://github.blog/changelog/2022-10-20-manage-caches-in-your-actions-workflows-from-web-interface/)
52 | or by setting the `CACHE_VERSION` secret to the current date (which will
53 | invalidate all of the caches).
54 |
55 | ## Updates
56 |
57 | ### Setup Information
58 |
59 | These workflows run on a schedule and at the maintainer's request. Because they
60 | create pull requests that update workflows/require the downstream actions to run,
61 | they need a special repository/organization secret token called
62 | `SANDPAPER_WORKFLOW` and it must have the `public_repo` and `workflow` scope.
63 |
64 | This can be an individual user token, OR it can be a trusted bot account. If you
65 | have a repository in one of the official Carpentries accounts, then you do not
66 | need to worry about this token being present because the Carpentries Core Team
67 | will take care of supplying this token.
68 |
69 | If you want to use your personal account: you can go to
70 |
71 | to create a token. Once you have created your token, you should copy it to your
72 | clipboard and then go to your repository's settings > secrets > actions and
73 | create or edit the `SANDPAPER_WORKFLOW` secret, pasting in the generated token.
74 |
75 | If you do not specify your token correctly, the runs will not fail and they will
76 | give you instructions to provide the token for your repository.
77 |
78 | ### 02 Maintain: Update Workflow Files (update-workflow.yaml)
79 |
80 | The {sandpaper} repository was designed to do as much as possible to separate
81 | the tools from the content. For local builds, this is absolutely true, but
82 | there is a minor issue when it comes to workflow files: they must live inside
83 | the repository.
84 |
85 | This workflow ensures that the workflow files are up-to-date. The way it work is
86 | to download the update-workflows.sh script from GitHub and run it. The script
87 | will do the following:
88 |
89 | 1. check the recorded version of sandpaper against the current version on github
90 | 2. update the files if there is a difference in versions
91 |
92 | After the files are updated, if there are any changes, they are pushed to a
93 | branch called `update/workflows` and a pull request is created. Maintainers are
94 | encouraged to review the changes and accept the pull request if the outputs
95 | are okay.
96 |
97 | This update is run weekly or on demand.
98 |
99 | ### 03 Maintain: Update Package Cache (update-cache.yaml)
100 |
101 | For lessons that have generated content, we use {renv} to ensure that the output
102 | is stable. This is controlled by a single lockfile which documents the packages
103 | needed for the lesson and the version numbers. This workflow is skipped in
104 | lessons that do not have generated content.
105 |
106 | Because the lessons need to remain current with the package ecosystem, it's a
107 | good idea to make sure these packages can be updated periodically. The
108 | update cache workflow will do this by checking for updates, applying them in a
109 | branch called `updates/packages` and creating a pull request with _only the
110 | lockfile changed_.
111 |
112 | From here, the markdown documents will be rebuilt and you can inspect what has
113 | changed based on how the packages have updated.
114 |
115 | ## Pull Request and Review Management
116 |
117 | Because our lessons execute code, pull requests are a secruity risk for any
118 | lesson and thus have security measures associted with them. **Do not merge any
119 | pull requests that do not pass checks and do not have bots commented on them.**
120 |
121 | This series of workflows all go together and are described in the following
122 | diagram and the below sections:
123 |
124 | 
125 |
126 | ### Pre Flight Pull Request Validation (pr-preflight.yaml)
127 |
128 | This workflow runs every time a pull request is created and its purpose is to
129 | validate that the pull request is okay to run. This means the following things:
130 |
131 | 1. The pull request does not contain modified workflow files
132 | 2. If the pull request contains modified workflow files, it does not contain
133 | modified content files (such as a situation where @carpentries-bot will
134 | make an automated pull request)
135 | 3. The pull request does not contain an invalid commit hash (e.g. from a fork
136 | that was made before a lesson was transitioned from styles to use the
137 | workbench).
138 |
139 | Once the checks are finished, a comment is issued to the pull request, which
140 | will allow maintainers to determine if it is safe to run the
141 | "Receive Pull Request" workflow from new contributors.
142 |
143 | ### Receive Pull Request (pr-receive.yaml)
144 |
145 | **Note of caution:** This workflow runs arbitrary code by anyone who creates a
146 | pull request. GitHub has safeguarded the token used in this workflow to have no
147 | priviledges in the repository, but we have taken precautions to protect against
148 | spoofing.
149 |
150 | This workflow is triggered with every push to a pull request. If this workflow
151 | is already running and a new push is sent to the pull request, the workflow
152 | running from the previous push will be cancelled and a new workflow run will be
153 | started.
154 |
155 | The first step of this workflow is to check if it is valid (e.g. that no
156 | workflow files have been modified). If there are workflow files that have been
157 | modified, a comment is made that indicates that the workflow is not run. If
158 | both a workflow file and lesson content is modified, an error will occurr.
159 |
160 | The second step (if valid) is to build the generated content from the pull
161 | request. This builds the content and uploads three artifacts:
162 |
163 | 1. The pull request number (pr)
164 | 2. A summary of changes after the rendering process (diff)
165 | 3. The rendered files (build)
166 |
167 | Because this workflow builds generated content, it follows the same general
168 | process as the `sandpaper-main` workflow with the same caching mechanisms.
169 |
170 | The artifacts produced are used by the next workflow.
171 |
172 | ### Comment on Pull Request (pr-comment.yaml)
173 |
174 | This workflow is triggered if the `pr-receive.yaml` workflow is successful.
175 | The steps in this workflow are:
176 |
177 | 1. Test if the workflow is valid and comment the validity of the workflow to the
178 | pull request.
179 | 2. If it is valid: create an orphan branch with two commits: the current state
180 | of the repository and the proposed changes.
181 | 3. If it is valid: update the pull request comment with the summary of changes
182 |
183 | Importantly: if the pull request is invalid, the branch is not created so any
184 | malicious code is not published.
185 |
186 | From here, the maintainer can request changes from the author and eventually
187 | either merge or reject the PR. When this happens, if the PR was valid, the
188 | preview branch needs to be deleted.
189 |
190 | ### Send Close PR Signal (pr-close-signal.yaml)
191 |
192 | Triggered any time a pull request is closed. This emits an artifact that is the
193 | pull request number for the next action
194 |
195 | ### Remove Pull Request Branch (pr-post-remove-branch.yaml)
196 |
197 | Tiggered by `pr-close-signal.yaml`. This removes the temporary branch associated with
198 | the pull request (if it was created).
199 |
--------------------------------------------------------------------------------
/.github/workflows/pr-close-signal.yaml:
--------------------------------------------------------------------------------
1 | name: "Bot: Send Close Pull Request Signal"
2 |
3 | on:
4 | pull_request:
5 | types:
6 | [closed]
7 |
8 | jobs:
9 | send-close-signal:
10 | name: "Send closing signal"
11 | runs-on: ubuntu-22.04
12 | if: ${{ github.event.action == 'closed' }}
13 | steps:
14 | - name: "Create PRtifact"
15 | run: |
16 | mkdir -p ./pr
17 | printf ${{ github.event.number }} > ./pr/NUM
18 | - name: Upload Diff
19 | uses: actions/upload-artifact@v4
20 | with:
21 | name: pr
22 | path: ./pr
23 |
--------------------------------------------------------------------------------
/.github/workflows/pr-comment.yaml:
--------------------------------------------------------------------------------
1 | name: "Bot: Comment on the Pull Request"
2 |
3 | # read-write repo token
4 | # access to secrets
5 | on:
6 | workflow_run:
7 | workflows: ["Receive Pull Request"]
8 | types:
9 | - completed
10 |
11 | concurrency:
12 | group: pr-${{ github.event.workflow_run.pull_requests[0].number }}
13 | cancel-in-progress: true
14 |
15 |
16 | jobs:
17 | # Pull requests are valid if:
18 | # - they match the sha of the workflow run head commit
19 | # - they are open
20 | # - no .github files were committed
21 | test-pr:
22 | name: "Test if pull request is valid"
23 | runs-on: ubuntu-22.04
24 | if: >
25 | github.event.workflow_run.event == 'pull_request' &&
26 | github.event.workflow_run.conclusion == 'success'
27 | outputs:
28 | is_valid: ${{ steps.check-pr.outputs.VALID }}
29 | payload: ${{ steps.check-pr.outputs.payload }}
30 | number: ${{ steps.get-pr.outputs.NUM }}
31 | msg: ${{ steps.check-pr.outputs.MSG }}
32 | steps:
33 | - name: 'Download PR artifact'
34 | id: dl
35 | uses: carpentries/actions/download-workflow-artifact@main
36 | with:
37 | run: ${{ github.event.workflow_run.id }}
38 | name: 'pr'
39 |
40 | - name: "Get PR Number"
41 | if: ${{ steps.dl.outputs.success == 'true' }}
42 | id: get-pr
43 | run: |
44 | unzip pr.zip
45 | echo "NUM=$(<./NR)" >> $GITHUB_OUTPUT
46 |
47 | - name: "Fail if PR number was not present"
48 | id: bad-pr
49 | if: ${{ steps.dl.outputs.success != 'true' }}
50 | run: |
51 | echo '::error::A pull request number was not recorded. The pull request that triggered this workflow is likely malicious.'
52 | exit 1
53 | - name: "Get Invalid Hashes File"
54 | id: hash
55 | run: |
56 | echo "json<> $GITHUB_OUTPUT
59 | - name: "Check PR"
60 | id: check-pr
61 | if: ${{ steps.dl.outputs.success == 'true' }}
62 | uses: carpentries/actions/check-valid-pr@main
63 | with:
64 | pr: ${{ steps.get-pr.outputs.NUM }}
65 | sha: ${{ github.event.workflow_run.head_sha }}
66 | headroom: 3 # if it's within the last three commits, we can keep going, because it's likely rapid-fire
67 | invalid: ${{ fromJSON(steps.hash.outputs.json)[github.repository] }}
68 | fail_on_error: true
69 |
70 | # Create an orphan branch on this repository with two commits
71 | # - the current HEAD of the md-outputs branch
72 | # - the output from running the current HEAD of the pull request through
73 | # the md generator
74 | create-branch:
75 | name: "Create Git Branch"
76 | needs: test-pr
77 | runs-on: ubuntu-22.04
78 | if: ${{ needs.test-pr.outputs.is_valid == 'true' }}
79 | env:
80 | NR: ${{ needs.test-pr.outputs.number }}
81 | permissions:
82 | contents: write
83 | steps:
84 | - name: 'Checkout md outputs'
85 | uses: actions/checkout@v4
86 | with:
87 | ref: md-outputs
88 | path: built
89 | fetch-depth: 1
90 |
91 | - name: 'Download built markdown'
92 | id: dl
93 | uses: carpentries/actions/download-workflow-artifact@main
94 | with:
95 | run: ${{ github.event.workflow_run.id }}
96 | name: 'built'
97 |
98 | - if: ${{ steps.dl.outputs.success == 'true' }}
99 | run: unzip built.zip
100 |
101 | - name: "Create orphan and push"
102 | if: ${{ steps.dl.outputs.success == 'true' }}
103 | run: |
104 | cd built/
105 | git config --local user.email "actions@github.com"
106 | git config --local user.name "GitHub Actions"
107 | CURR_HEAD=$(git rev-parse HEAD)
108 | git checkout --orphan md-outputs-PR-${NR}
109 | git add -A
110 | git commit -m "source commit: ${CURR_HEAD}"
111 | ls -A | grep -v '^.git$' | xargs -I _ rm -r '_'
112 | cd ..
113 | unzip -o -d built built.zip
114 | cd built
115 | git add -A
116 | git commit --allow-empty -m "differences for PR #${NR}"
117 | git push -u --force --set-upstream origin md-outputs-PR-${NR}
118 |
119 | # Comment on the Pull Request with a link to the branch and the diff
120 | comment-pr:
121 | name: "Comment on Pull Request"
122 | needs: [test-pr, create-branch]
123 | runs-on: ubuntu-22.04
124 | if: ${{ needs.test-pr.outputs.is_valid == 'true' }}
125 | env:
126 | NR: ${{ needs.test-pr.outputs.number }}
127 | permissions:
128 | pull-requests: write
129 | steps:
130 | - name: 'Download comment artifact'
131 | id: dl
132 | uses: carpentries/actions/download-workflow-artifact@main
133 | with:
134 | run: ${{ github.event.workflow_run.id }}
135 | name: 'diff'
136 |
137 | - if: ${{ steps.dl.outputs.success == 'true' }}
138 | run: unzip ${{ github.workspace }}/diff.zip
139 |
140 | - name: "Comment on PR"
141 | id: comment-diff
142 | if: ${{ steps.dl.outputs.success == 'true' }}
143 | uses: carpentries/actions/comment-diff@main
144 | with:
145 | pr: ${{ env.NR }}
146 | path: ${{ github.workspace }}/diff.md
147 |
148 | # Comment if the PR is open and matches the SHA, but the workflow files have
149 | # changed
150 | comment-changed-workflow:
151 | name: "Comment if workflow files have changed"
152 | needs: test-pr
153 | runs-on: ubuntu-22.04
154 | if: ${{ always() && needs.test-pr.outputs.is_valid == 'false' }}
155 | env:
156 | NR: ${{ github.event.workflow_run.pull_requests[0].number }}
157 | body: ${{ needs.test-pr.outputs.msg }}
158 | permissions:
159 | pull-requests: write
160 | steps:
161 | - name: 'Check for spoofing'
162 | id: dl
163 | uses: carpentries/actions/download-workflow-artifact@main
164 | with:
165 | run: ${{ github.event.workflow_run.id }}
166 | name: 'built'
167 |
168 | - name: 'Alert if spoofed'
169 | id: spoof
170 | if: ${{ steps.dl.outputs.success == 'true' }}
171 | run: |
172 | echo 'body<> $GITHUB_ENV
173 | echo '' >> $GITHUB_ENV
174 | echo '## :x: DANGER :x:' >> $GITHUB_ENV
175 | echo 'This pull request has modified workflows that created output. Close this now.' >> $GITHUB_ENV
176 | echo '' >> $GITHUB_ENV
177 | echo 'EOF' >> $GITHUB_ENV
178 |
179 | - name: "Comment on PR"
180 | id: comment-diff
181 | uses: carpentries/actions/comment-diff@main
182 | with:
183 | pr: ${{ env.NR }}
184 | body: ${{ env.body }}
185 |
--------------------------------------------------------------------------------
/.github/workflows/pr-post-remove-branch.yaml:
--------------------------------------------------------------------------------
1 | name: "Bot: Remove Temporary PR Branch"
2 |
3 | on:
4 | workflow_run:
5 | workflows: ["Bot: Send Close Pull Request Signal"]
6 | types:
7 | - completed
8 |
9 | jobs:
10 | delete:
11 | name: "Delete branch from Pull Request"
12 | runs-on: ubuntu-22.04
13 | if: >
14 | github.event.workflow_run.event == 'pull_request' &&
15 | github.event.workflow_run.conclusion == 'success'
16 | permissions:
17 | contents: write
18 | steps:
19 | - name: 'Download artifact'
20 | uses: carpentries/actions/download-workflow-artifact@main
21 | with:
22 | run: ${{ github.event.workflow_run.id }}
23 | name: pr
24 | - name: "Get PR Number"
25 | id: get-pr
26 | run: |
27 | unzip pr.zip
28 | echo "NUM=$(<./NUM)" >> $GITHUB_OUTPUT
29 | - name: 'Remove branch'
30 | uses: carpentries/actions/remove-branch@main
31 | with:
32 | pr: ${{ steps.get-pr.outputs.NUM }}
33 |
--------------------------------------------------------------------------------
/.github/workflows/pr-preflight.yaml:
--------------------------------------------------------------------------------
1 | name: "Pull Request Preflight Check"
2 |
3 | on:
4 | pull_request_target:
5 | branches:
6 | ["main"]
7 | types:
8 | ["opened", "synchronize", "reopened"]
9 |
10 | jobs:
11 | test-pr:
12 | name: "Test if pull request is valid"
13 | if: ${{ github.event.action != 'closed' }}
14 | runs-on: ubuntu-22.04
15 | outputs:
16 | is_valid: ${{ steps.check-pr.outputs.VALID }}
17 | permissions:
18 | pull-requests: write
19 | steps:
20 | - name: "Get Invalid Hashes File"
21 | id: hash
22 | run: |
23 | echo "json<> $GITHUB_OUTPUT
26 | - name: "Check PR"
27 | id: check-pr
28 | uses: carpentries/actions/check-valid-pr@main
29 | with:
30 | pr: ${{ github.event.number }}
31 | invalid: ${{ fromJSON(steps.hash.outputs.json)[github.repository] }}
32 | fail_on_error: true
33 | - name: "Comment result of validation"
34 | id: comment-diff
35 | if: ${{ always() }}
36 | uses: carpentries/actions/comment-diff@main
37 | with:
38 | pr: ${{ github.event.number }}
39 | body: ${{ steps.check-pr.outputs.MSG }}
40 |
--------------------------------------------------------------------------------
/.github/workflows/pr-receive.yaml:
--------------------------------------------------------------------------------
1 | name: "Receive Pull Request"
2 |
3 | on:
4 | pull_request:
5 | types:
6 | [opened, synchronize, reopened]
7 |
8 | concurrency:
9 | group: ${{ github.ref }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | test-pr:
14 | name: "Record PR number"
15 | if: ${{ github.event.action != 'closed' }}
16 | runs-on: ubuntu-22.04
17 | outputs:
18 | is_valid: ${{ steps.check-pr.outputs.VALID }}
19 | steps:
20 | - name: "Record PR number"
21 | id: record
22 | if: ${{ always() }}
23 | run: |
24 | echo ${{ github.event.number }} > ${{ github.workspace }}/NR # 2022-03-02: artifact name fixed to be NR
25 | - name: "Upload PR number"
26 | id: upload
27 | if: ${{ always() }}
28 | uses: actions/upload-artifact@v4
29 | with:
30 | name: pr
31 | path: ${{ github.workspace }}/NR
32 | - name: "Get Invalid Hashes File"
33 | id: hash
34 | run: |
35 | echo "json<> $GITHUB_OUTPUT
38 | - name: "echo output"
39 | run: |
40 | echo "${{ steps.hash.outputs.json }}"
41 | - name: "Check PR"
42 | id: check-pr
43 | uses: carpentries/actions/check-valid-pr@main
44 | with:
45 | pr: ${{ github.event.number }}
46 | invalid: ${{ fromJSON(steps.hash.outputs.json)[github.repository] }}
47 |
48 | build-md-source:
49 | name: "Build markdown source files if valid"
50 | needs: test-pr
51 | runs-on: ubuntu-22.04
52 | if: ${{ needs.test-pr.outputs.is_valid == 'true' }}
53 | env:
54 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
55 | RENV_PATHS_ROOT: ~/.local/share/renv/
56 | CHIVE: ${{ github.workspace }}/site/chive
57 | PR: ${{ github.workspace }}/site/pr
58 | MD: ${{ github.workspace }}/site/built
59 | steps:
60 | - name: "Check Out Main Branch"
61 | uses: actions/checkout@v4
62 |
63 | - name: "Check Out Staging Branch"
64 | uses: actions/checkout@v4
65 | with:
66 | ref: md-outputs
67 | path: ${{ env.MD }}
68 |
69 | - name: "Set up R"
70 | uses: r-lib/actions/setup-r@v2
71 | with:
72 | use-public-rspm: true
73 | install-r: false
74 |
75 | - name: "Set up Pandoc"
76 | uses: r-lib/actions/setup-pandoc@v2
77 |
78 | - name: "Setup Lesson Engine"
79 | uses: carpentries/actions/setup-sandpaper@main
80 | with:
81 | cache-version: ${{ secrets.CACHE_VERSION }}
82 |
83 | - name: "Setup Package Cache"
84 | uses: carpentries/actions/setup-lesson-deps@main
85 | with:
86 | cache-version: ${{ secrets.CACHE_VERSION }}
87 |
88 | - name: "Validate and Build Markdown"
89 | id: build-site
90 | run: |
91 | sandpaper::package_cache_trigger(TRUE)
92 | sandpaper::validate_lesson(path = '${{ github.workspace }}')
93 | sandpaper:::build_markdown(path = '${{ github.workspace }}', quiet = FALSE)
94 | shell: Rscript {0}
95 |
96 | - name: "Generate Artifacts"
97 | id: generate-artifacts
98 | run: |
99 | sandpaper:::ci_bundle_pr_artifacts(
100 | repo = '${{ github.repository }}',
101 | pr_number = '${{ github.event.number }}',
102 | path_md = '${{ env.MD }}',
103 | path_pr = '${{ env.PR }}',
104 | path_archive = '${{ env.CHIVE }}',
105 | branch = 'md-outputs'
106 | )
107 | shell: Rscript {0}
108 |
109 | - name: "Upload PR"
110 | uses: actions/upload-artifact@v4
111 | with:
112 | name: pr
113 | path: ${{ env.PR }}
114 | overwrite: true
115 |
116 | - name: "Upload Diff"
117 | uses: actions/upload-artifact@v4
118 | with:
119 | name: diff
120 | path: ${{ env.CHIVE }}
121 | retention-days: 1
122 |
123 | - name: "Upload Build"
124 | uses: actions/upload-artifact@v4
125 | with:
126 | name: built
127 | path: ${{ env.MD }}
128 | retention-days: 1
129 |
130 | - name: "Teardown"
131 | run: sandpaper::reset_site()
132 | shell: Rscript {0}
133 |
--------------------------------------------------------------------------------
/.github/workflows/sandpaper-main.yaml:
--------------------------------------------------------------------------------
1 | name: "01 Build and Deploy Site"
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 | schedule:
9 | - cron: '0 0 * * 2'
10 | workflow_dispatch:
11 | inputs:
12 | name:
13 | description: 'Who triggered this build?'
14 | required: true
15 | default: 'Maintainer (via GitHub)'
16 | reset:
17 | description: 'Reset cached markdown files'
18 | required: false
19 | default: false
20 | type: boolean
21 | jobs:
22 | full-build:
23 | name: "Build Full Site"
24 |
25 | # 2024-10-01: ubuntu-latest is now 24.04 and R is not installed by default in the runner image
26 | # pin to 22.04 for now
27 | runs-on: ubuntu-22.04
28 | permissions:
29 | checks: write
30 | contents: write
31 | pages: write
32 | env:
33 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
34 | RENV_PATHS_ROOT: ~/.local/share/renv/
35 | steps:
36 |
37 | - name: "Checkout Lesson"
38 | uses: actions/checkout@v4
39 |
40 | - name: "Set up R"
41 | uses: r-lib/actions/setup-r@v2
42 | with:
43 | use-public-rspm: true
44 | install-r: false
45 |
46 | - name: "Set up Pandoc"
47 | uses: r-lib/actions/setup-pandoc@v2
48 |
49 | - name: "Setup Lesson Engine"
50 | uses: carpentries/actions/setup-sandpaper@main
51 | with:
52 | cache-version: ${{ secrets.CACHE_VERSION }}
53 |
54 | - name: "Setup Package Cache"
55 | uses: carpentries/actions/setup-lesson-deps@main
56 | with:
57 | cache-version: ${{ secrets.CACHE_VERSION }}
58 |
59 | - name: "Deploy Site"
60 | run: |
61 | reset <- "${{ github.event.inputs.reset }}" == "true"
62 | sandpaper::package_cache_trigger(TRUE)
63 | sandpaper:::ci_deploy(reset = reset)
64 | shell: Rscript {0}
65 |
--------------------------------------------------------------------------------
/.github/workflows/sandpaper-version.txt:
--------------------------------------------------------------------------------
1 | 0.16.9
2 |
--------------------------------------------------------------------------------
/.github/workflows/update-cache.yaml:
--------------------------------------------------------------------------------
1 | name: "03 Maintain: Update Package Cache"
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | name:
7 | description: 'Who triggered this build (enter github username to tag yourself)?'
8 | required: true
9 | default: 'monthly run'
10 | schedule:
11 | # Run every tuesday
12 | - cron: '0 0 * * 2'
13 |
14 | jobs:
15 | preflight:
16 | name: "Preflight Check"
17 | runs-on: ubuntu-22.04
18 | outputs:
19 | ok: ${{ steps.check.outputs.ok }}
20 | steps:
21 | - id: check
22 | run: |
23 | if [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then
24 | echo "ok=true" >> $GITHUB_OUTPUT
25 | echo "Running on request"
26 | # using single brackets here to avoid 08 being interpreted as octal
27 | # https://github.com/carpentries/sandpaper/issues/250
28 | elif [ `date +%d` -le 7 ]; then
29 | # If the Tuesday lands in the first week of the month, run it
30 | echo "ok=true" >> $GITHUB_OUTPUT
31 | echo "Running on schedule"
32 | else
33 | echo "ok=false" >> $GITHUB_OUTPUT
34 | echo "Not Running Today"
35 | fi
36 |
37 | check_renv:
38 | name: "Check if We Need {renv}"
39 | runs-on: ubuntu-22.04
40 | needs: preflight
41 | if: ${{ needs.preflight.outputs.ok == 'true'}}
42 | outputs:
43 | needed: ${{ steps.renv.outputs.exists }}
44 | steps:
45 | - name: "Checkout Lesson"
46 | uses: actions/checkout@v4
47 | - id: renv
48 | run: |
49 | if [[ -d renv ]]; then
50 | echo "exists=true" >> $GITHUB_OUTPUT
51 | fi
52 |
53 | check_token:
54 | name: "Check SANDPAPER_WORKFLOW token"
55 | runs-on: ubuntu-22.04
56 | needs: check_renv
57 | if: ${{ needs.check_renv.outputs.needed == 'true' }}
58 | outputs:
59 | workflow: ${{ steps.validate.outputs.wf }}
60 | repo: ${{ steps.validate.outputs.repo }}
61 | steps:
62 | - name: "validate token"
63 | id: validate
64 | uses: carpentries/actions/check-valid-credentials@main
65 | with:
66 | token: ${{ secrets.SANDPAPER_WORKFLOW }}
67 |
68 | update_cache:
69 | name: "Update Package Cache"
70 | needs: check_token
71 | if: ${{ needs.check_token.outputs.repo== 'true' }}
72 | runs-on: ubuntu-22.04
73 | env:
74 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
75 | RENV_PATHS_ROOT: ~/.local/share/renv/
76 | steps:
77 |
78 | - name: "Checkout Lesson"
79 | uses: actions/checkout@v4
80 |
81 | - name: "Set up R"
82 | uses: r-lib/actions/setup-r@v2
83 | with:
84 | use-public-rspm: true
85 | install-r: false
86 |
87 | - name: "Update {renv} deps and determine if a PR is needed"
88 | id: update
89 | uses: carpentries/actions/update-lockfile@main
90 | with:
91 | cache-version: ${{ secrets.CACHE_VERSION }}
92 |
93 | - name: Create Pull Request
94 | id: cpr
95 | if: ${{ steps.update.outputs.n > 0 }}
96 | uses: carpentries/create-pull-request@main
97 | with:
98 | token: ${{ secrets.SANDPAPER_WORKFLOW }}
99 | delete-branch: true
100 | branch: "update/packages"
101 | commit-message: "[actions] update ${{ steps.update.outputs.n }} packages"
102 | title: "Update ${{ steps.update.outputs.n }} packages"
103 | body: |
104 | :robot: This is an automated build
105 |
106 | This will update ${{ steps.update.outputs.n }} packages in your lesson with the following versions:
107 |
108 | ```
109 | ${{ steps.update.outputs.report }}
110 | ```
111 |
112 | :stopwatch: In a few minutes, a comment will appear that will show you how the output has changed based on these updates.
113 |
114 | If you want to inspect these changes locally, you can use the following code to check out a new branch:
115 |
116 | ```bash
117 | git fetch origin update/packages
118 | git checkout update/packages
119 | ```
120 |
121 | - Auto-generated by [create-pull-request][1] on ${{ steps.update.outputs.date }}
122 |
123 | [1]: https://github.com/carpentries/create-pull-request/tree/main
124 | labels: "type: package cache"
125 | draft: false
126 |
--------------------------------------------------------------------------------
/.github/workflows/update-workflows.yaml:
--------------------------------------------------------------------------------
1 | name: "02 Maintain: Update Workflow Files"
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | name:
7 | description: 'Who triggered this build (enter github username to tag yourself)?'
8 | required: true
9 | default: 'weekly run'
10 | clean:
11 | description: 'Workflow files/file extensions to clean (no wildcards, enter "" for none)'
12 | required: false
13 | default: '.yaml'
14 | schedule:
15 | # Run every Tuesday
16 | - cron: '0 0 * * 2'
17 |
18 | jobs:
19 | check_token:
20 | name: "Check SANDPAPER_WORKFLOW token"
21 | runs-on: ubuntu-22.04
22 | outputs:
23 | workflow: ${{ steps.validate.outputs.wf }}
24 | repo: ${{ steps.validate.outputs.repo }}
25 | steps:
26 | - name: "validate token"
27 | id: validate
28 | uses: carpentries/actions/check-valid-credentials@main
29 | with:
30 | token: ${{ secrets.SANDPAPER_WORKFLOW }}
31 |
32 | update_workflow:
33 | name: "Update Workflow"
34 | runs-on: ubuntu-22.04
35 | needs: check_token
36 | if: ${{ needs.check_token.outputs.workflow == 'true' }}
37 | steps:
38 | - name: "Checkout Repository"
39 | uses: actions/checkout@v4
40 |
41 | - name: Update Workflows
42 | id: update
43 | uses: carpentries/actions/update-workflows@main
44 | with:
45 | clean: ${{ github.event.inputs.clean }}
46 |
47 | - name: Create Pull Request
48 | id: cpr
49 | if: "${{ steps.update.outputs.new }}"
50 | uses: carpentries/create-pull-request@main
51 | with:
52 | token: ${{ secrets.SANDPAPER_WORKFLOW }}
53 | delete-branch: true
54 | branch: "update/workflows"
55 | commit-message: "[actions] update sandpaper workflow to version ${{ steps.update.outputs.new }}"
56 | title: "Update Workflows to Version ${{ steps.update.outputs.new }}"
57 | body: |
58 | :robot: This is an automated build
59 |
60 | Update Workflows from sandpaper version ${{ steps.update.outputs.old }} -> ${{ steps.update.outputs.new }}
61 |
62 | - Auto-generated by [create-pull-request][1] on ${{ steps.update.outputs.date }}
63 |
64 | [1]: https://github.com/carpentries/create-pull-request/tree/main
65 | labels: "type: template and tools"
66 | draft: false
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # sandpaper files
2 | episodes/*html
3 | site/*
4 | !site/README.md
5 |
6 | # History files
7 | .Rhistory
8 | .Rapp.history
9 | # Session Data files
10 | .RData
11 | # User-specific files
12 | .Ruserdata
13 | # Example code in package build process
14 | *-Ex.R
15 | # Output files from R CMD build
16 | /*.tar.gz
17 | # Output files from R CMD check
18 | /*.Rcheck/
19 | # RStudio files
20 | .Rproj.user/
21 | # produced vignettes
22 | vignettes/*.html
23 | vignettes/*.pdf
24 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
25 | .httr-oauth
26 | # knitr and R markdown default cache directories
27 | *_cache/
28 | /cache/
29 | # Temporary files created by R markdown
30 | *.utf8.md
31 | *.knit.md
32 | # R Environment Variables
33 | .Renviron
34 | # pkgdown site
35 | docs/
36 | # translation temp files
37 | po/*~
38 | # renv detritus
39 | renv/sandbox/
40 | *.pyc
41 | *~
42 | .DS_Store
43 | .ipynb_checkpoints
44 | .sass-cache
45 | .jekyll-cache/
46 | .jekyll-metadata
47 | __pycache__
48 | _site
49 | .Rproj.user
50 | .bundle/
51 | .vendor/
52 | vendor/
53 | .docker-vendor/
54 | Gemfile.lock
55 | .*history
56 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Erin Becker
2 | Azalee Bostroem
3 | Allen Downey
4 | Zhian N. Kamvar
5 | Ralf Kotulla
6 | Catherine Martlin
7 | Rodolfo Montez Jr.
8 | Leon Oostrum
9 | Annajiat Alim Rasel
10 | Phil Rosenfield
11 | Erik Tollerud
12 |
13 | rlucas-scinet
14 | bmg-pcl
--------------------------------------------------------------------------------
/CITATION:
--------------------------------------------------------------------------------
1 | Cite as:
2 |
3 | Downey, Allen, Bostroem, K. Azalee, Becker, Erin A, Montez, Rodolfo, & Rosenfield, Philip. (2022, April 6). https://datacarpentry.org/astronomy-python/: Data Carpentry: Foundations of Astronomical Data Science, April 2022. Zenodo. https://doi.org/10.5281/zenodo.6419749
4 |
5 | [](https://doi.org/10.5281/zenodo.6419749)
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Contributor Code of Conduct"
3 | ---
4 |
5 | As contributors and maintainers of this project,
6 | we pledge to follow the [The Carpentries Code of Conduct][coc].
7 |
8 | Instances of abusive, harassing, or otherwise unacceptable behavior
9 | may be reported by following our [reporting guidelines][coc-reporting].
10 |
11 |
12 | [coc-reporting]: https://docs.carpentries.org/topic_folders/policies/incident-reporting.html
13 | [coc]: https://docs.carpentries.org/topic_folders/policies/code-of-conduct.html
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | [The Carpentries][cp-site] ([Software Carpentry][swc-site], [Data
4 | Carpentry][dc-site], and [Library Carpentry][lc-site]) are open source
5 | projects, and we welcome contributions of all kinds: new lessons, fixes to
6 | existing material, bug reports, and reviews of proposed changes are all
7 | welcome.
8 |
9 | ### Contributor Agreement
10 |
11 | By contributing, you agree that we may redistribute your work under [our
12 | license](LICENSE.md). In exchange, we will address your issues and/or assess
13 | your change proposal as promptly as we can, and help you become a member of our
14 | community. Everyone involved in [The Carpentries][cp-site] agrees to abide by
15 | our [code of conduct](CODE_OF_CONDUCT.md).
16 |
17 | ### How to Contribute
18 |
19 | The easiest way to get started is to file an issue to tell us about a spelling
20 | mistake, some awkward wording, or a factual error. This is a good way to
21 | introduce yourself and to meet some of our community members.
22 |
23 | 1. If you do not have a [GitHub][github] account, you can [send us comments by
24 | email][contact]. However, we will be able to respond more quickly if you use
25 | one of the other methods described below.
26 |
27 | 2. If you have a [GitHub][github] account, or are willing to [create
28 | one][github-join], but do not know how to use Git, you can report problems
29 | or suggest improvements by [creating an issue][repo-issues]. This allows us
30 | to assign the item to someone and to respond to it in a threaded discussion.
31 |
32 | 3. If you are comfortable with Git, and would like to add or change material,
33 | you can submit a pull request (PR). Instructions for doing this are
34 | [included below](#using-github). For inspiration about changes that need to
35 | be made, check out the [list of open issues][issues] across the Carpentries.
36 |
37 | Note: if you want to build the website locally, please refer to [The Workbench
38 | documentation][template-doc].
39 |
40 | ### Where to Contribute
41 |
42 | 1. If you wish to change this lesson, add issues and pull requests here.
43 | 2. If you wish to change the template used for workshop websites, please refer
44 | to [The Workbench documentation][template-doc].
45 |
46 |
47 | ### What to Contribute
48 |
49 | There are many ways to contribute, from writing new exercises and improving
50 | existing ones to updating or filling in the documentation and submitting [bug
51 | reports][issues] about things that do not work, are not clear, or are missing.
52 | If you are looking for ideas, please see [the list of issues for this
53 | repository][repo-issues], or the issues for [Data Carpentry][dc-issues],
54 | [Library Carpentry][lc-issues], and [Software Carpentry][swc-issues] projects.
55 |
56 | Comments on issues and reviews of pull requests are just as welcome: we are
57 | smarter together than we are on our own. **Reviews from novices and newcomers
58 | are particularly valuable**: it's easy for people who have been using these
59 | lessons for a while to forget how impenetrable some of this material can be, so
60 | fresh eyes are always welcome.
61 |
62 | ### What *Not* to Contribute
63 |
64 | Our lessons already contain more material than we can cover in a typical
65 | workshop, so we are usually *not* looking for more concepts or tools to add to
66 | them. As a rule, if you want to introduce a new idea, you must (a) estimate how
67 | long it will take to teach and (b) explain what you would take out to make room
68 | for it. The first encourages contributors to be honest about requirements; the
69 | second, to think hard about priorities.
70 |
71 | We are also not looking for exercises or other material that only run on one
72 | platform. Our workshops typically contain a mixture of Windows, macOS, and
73 | Linux users; in order to be usable, our lessons must run equally well on all
74 | three.
75 |
76 | ### Using GitHub
77 |
78 | If you choose to contribute via GitHub, you may want to look at [How to
79 | Contribute to an Open Source Project on GitHub][how-contribute]. In brief, we
80 | use [GitHub flow][github-flow] to manage changes:
81 |
82 | 1. Create a new branch in your desktop copy of this repository for each
83 | significant change.
84 | 2. Commit the change in that branch.
85 | 3. Push that branch to your fork of this repository on GitHub.
86 | 4. Submit a pull request from that branch to the [upstream repository][repo].
87 | 5. If you receive feedback, make changes on your desktop and push to your
88 | branch on GitHub: the pull request will update automatically.
89 |
90 | NB: The published copy of the lesson is usually in the `main` branch.
91 |
92 | Each lesson has a team of maintainers who review issues and pull requests or
93 | encourage others to do so. The maintainers are community volunteers, and have
94 | final say over what gets merged into the lesson.
95 |
96 | ### Other Resources
97 |
98 | The Carpentries is a global organisation with volunteers and learners all over
99 | the world. We share values of inclusivity and a passion for sharing knowledge,
100 | teaching and learning. There are several ways to connect with The Carpentries
101 | community listed at including via social
102 | media, slack, newsletters, and email lists. You can also [reach us by
103 | email][contact].
104 |
105 | [repo]: https://datacarpentry.org/astronomy-python/
106 | [repo-issues]: https://datacarpentry.org/astronomy-python/issues
107 | [contact]: mailto:team@carpentries.org
108 | [cp-site]: https://carpentries.org/
109 | [dc-issues]: https://github.com/issues?q=user%3Adatacarpentry
110 | [dc-lessons]: https://datacarpentry.org/lessons/
111 | [dc-site]: https://datacarpentry.org/
112 | [discuss-list]: https://lists.software-carpentry.org/listinfo/discuss
113 | [github]: https://github.com
114 | [github-flow]: https://guides.github.com/introduction/flow/
115 | [github-join]: https://github.com/join
116 | [how-contribute]: https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github
117 | [issues]: https://carpentries.org/help-wanted-issues/
118 | [lc-issues]: https://github.com/issues?q=user%3ALibraryCarpentry
119 | [swc-issues]: https://github.com/issues?q=user%3Aswcarpentry
120 | [swc-lessons]: https://software-carpentry.org/lessons/
121 | [swc-site]: https://software-carpentry.org/
122 | [lc-site]: https://librarycarpentry.org/
123 | [template-doc]: https://carpentries.github.io/workbench/
124 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Licenses"
3 | ---
4 |
5 | ## Instructional Material
6 |
7 | All Carpentries (Software Carpentry, Data Carpentry, and Library Carpentry)
8 | instructional material is made available under the [Creative Commons
9 | Attribution license][cc-by-human]. The following is a human-readable summary of
10 | (and not a substitute for) the [full legal text of the CC BY 4.0
11 | license][cc-by-legal].
12 |
13 | You are free:
14 |
15 | - to **Share**---copy and redistribute the material in any medium or format
16 | - to **Adapt**---remix, transform, and build upon the material
17 |
18 | for any purpose, even commercially.
19 |
20 | The licensor cannot revoke these freedoms as long as you follow the license
21 | terms.
22 |
23 | Under the following terms:
24 |
25 | - **Attribution**---You must give appropriate credit (mentioning that your work
26 | is derived from work that is Copyright (c) The Carpentries and, where
27 | practical, linking to ), provide a [link to the
28 | license][cc-by-human], and indicate if changes were made. You may do so in
29 | any reasonable manner, but not in any way that suggests the licensor endorses
30 | you or your use.
31 |
32 | - **No additional restrictions**---You may not apply legal terms or
33 | technological measures that legally restrict others from doing anything the
34 | license permits. With the understanding that:
35 |
36 | Notices:
37 |
38 | * You do not have to comply with the license for elements of the material in
39 | the public domain or where your use is permitted by an applicable exception
40 | or limitation.
41 | * No warranties are given. The license may not give you all of the permissions
42 | necessary for your intended use. For example, other rights such as publicity,
43 | privacy, or moral rights may limit how you use the material.
44 |
45 | ## Software
46 |
47 | Except where otherwise noted, the example programs and other software provided
48 | by The Carpentries are made available under the [OSI][osi]-approved [MIT
49 | license][mit-license].
50 |
51 | Permission is hereby granted, free of charge, to any person obtaining a copy of
52 | this software and associated documentation files (the "Software"), to deal in
53 | the Software without restriction, including without limitation the rights to
54 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
55 | of the Software, and to permit persons to whom the Software is furnished to do
56 | so, subject to the following conditions:
57 |
58 | The above copyright notice and this permission notice shall be included in all
59 | copies or substantial portions of the Software.
60 |
61 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
62 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
63 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
64 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
65 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
66 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
67 | SOFTWARE.
68 |
69 | ## Trademark
70 |
71 | "The Carpentries", "Software Carpentry", "Data Carpentry", and "Library
72 | Carpentry" and their respective logos are registered trademarks of
73 | [The Carpentries, Inc.][carpentries].
74 |
75 | [cc-by-human]: https://creativecommons.org/licenses/by/4.0/
76 | [cc-by-legal]: https://creativecommons.org/licenses/by/4.0/legalcode
77 | [mit-license]: https://opensource.org/licenses/mit-license.html
78 | [carpentries]: https://carpentries.org
79 | [osi]: https://opensource.org
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://doi.org/10.5281/zenodo.6419749)
2 |
3 | # Astronomy
4 |
5 | ## Contributing
6 |
7 | We welcome all contributions to improve the lesson! Maintainers will do their best to help you if you have any
8 | questions, concerns, or experience any difficulties along the way.
9 |
10 | We'd like to ask you to familiarize yourself with our [Contribution Guide](CONTRIBUTING.md) and have a look at
11 | the [more detailed guidelines][lesson-example] on proper formatting, ways to render the lesson locally, and even
12 | how to write new episodes.
13 |
14 | Please see the current list of [issues](https://github.com/datacarpentry/astronomy-python/issues) for ideas for contributing to this
15 | repository. For making your contribution, we use the GitHub flow, which is
16 | nicely explained in the chapter [Contributing to a Project](https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) in Pro Git
17 | by Scott Chacon.
18 | Look for the tag . This indicates that the maintainers will welcome a pull request fixing this issue.
19 |
20 | ## Maintainer(s)
21 |
22 | Current maintainers of this lesson are
23 |
24 | - [Azalee Bostroem](https://github.com/abostroem)
25 | - [Rodolfo Montez Jr.](https://github.com/rudyphd)
26 | - [Ralf Kotulla](https://github.com/rkotulla)
27 |
28 | ## Authors
29 |
30 | A list of contributors to the lesson can be found in [AUTHORS](AUTHORS)
31 |
32 | ## Citation
33 |
34 | To cite this lesson, please consult with [CITATION](CITATION)
35 |
36 | [lesson-example]: https://carpentries.github.io/lesson-example
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | #------------------------------------------------------------
2 | # Values for this lesson.
3 | #------------------------------------------------------------
4 |
5 | # Which carpentry is this (swc, dc, lc, or cp)?
6 | # swc: Software Carpentry
7 | # dc: Data Carpentry
8 | # lc: Library Carpentry
9 | # cp: Carpentries (to use for instructor training for instance)
10 | # incubator: The Carpentries Incubator
11 | carpentry: 'dc'
12 |
13 | # Overall title for pages.
14 | title: 'Foundations of Astronomical Data Science'
15 |
16 | # Date the lesson was created (YYYY-MM-DD, this is empty by default)
17 | created: '2020-05-11'
18 |
19 | # Comma-separated list of keywords for the lesson
20 | keywords: 'software, data, lesson, The Carpentries'
21 |
22 | # Life cycle stage of the lesson
23 | # possible values: pre-alpha, alpha, beta, stable
24 | life_cycle: 'stable'
25 |
26 | # License of the lesson materials (recommended CC-BY 4.0)
27 | license: 'CC-BY 4.0'
28 |
29 | # Link to the source repository for this lesson
30 | source: 'https://github.com/datacarpentry/astronomy-python'
31 |
32 | # Default branch of your lesson
33 | branch: 'main'
34 |
35 | # Who to contact if there are any issues
36 | contact: 'team@carpentries.org'
37 |
38 | # Navigation ------------------------------------------------
39 | #
40 | # Use the following menu items to specify the order of
41 | # individual pages in each dropdown section. Leave blank to
42 | # include all pages in the folder.
43 | #
44 | # Example -------------
45 | #
46 | # episodes:
47 | # - introduction.md
48 | # - first-steps.md
49 | #
50 | # learners:
51 | # - setup.md
52 | #
53 | # instructors:
54 | # - instructor-notes.md
55 | #
56 | # profiles:
57 | # - one-learner.md
58 | # - another-learner.md
59 |
60 | # Order of episodes in your lesson
61 | episodes:
62 | - 01-query.md
63 | - 02-coords.md
64 | - 03-transform.md
65 | - 04-motion.md
66 | - 05-select.md
67 | - 06-join.md
68 | - 07-photo.md
69 | - 08-plot.md
70 |
71 | # Information for Learners
72 | learners:
73 |
74 | # Information for Instructors
75 | instructors:
76 |
77 | # Learner Profiles
78 | profiles:
79 |
80 | # Customisation ---------------------------------------------
81 | #
82 | # This space below is where custom yaml items (e.g. pinning
83 | # sandpaper and varnish versions) should live
84 |
85 |
86 | url: 'https://datacarpentry.github.io/astronomy-python'
87 | analytics: carpentries
88 | lang: en
89 |
--------------------------------------------------------------------------------
/episodes/04-motion.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Plotting and Pandas
3 | teaching: 50
4 | exercises: 15
5 | ---
6 |
7 | ::::::::::::::::::::::::::::::::::::::: objectives
8 |
9 | - Use a Boolean Pandas `Series` to select rows in a `DataFrame`.
10 | - Save multiple `DataFrame`s in an HDF5 file.
11 |
12 | ::::::::::::::::::::::::::::::::::::::::::::::::::
13 |
14 | :::::::::::::::::::::::::::::::::::::::: questions
15 |
16 | - How do efficiently explore our data and identify appropriate filters to produce a clean sample (in this case of GD-1 stars)?
17 |
18 | ::::::::::::::::::::::::::::::::::::::::::::::::::
19 |
20 |
21 |
22 | In the previous episode, we wrote a query to select stars from the
23 | region of the sky where we expect GD-1 to be, and saved the results in
24 | a FITS and HDF5 file.
25 |
26 | Now we will read that data back in and implement the next step in the
27 | analysis, identifying stars with the proper motion we expect for GD-1.
28 |
29 | ::::::::::::::::::::::::::::::::::::::: checklist
30 |
31 | ## Outline
32 |
33 | 1. We will put those results into a Pandas `DataFrame`, which we will use
34 | to select stars near the centerline of GD-1.
35 |
36 | 2. Plotting the proper motion of those stars, we will identify a region
37 | of proper motion for stars that are likely to be in GD-1.
38 |
39 | 3. Finally, we will select and plot the stars whose proper motion is in
40 | that region.
41 |
42 |
43 | ::::::::::::::::::::::::::::::::::::::::::::::::::
44 |
45 | :::::::::::::::::::::::::::::::::::::::::: prereq
46 |
47 | ## Starting from this episode
48 |
49 | If you are starting a new notebook for this episode, expand this section
50 | for information you will need to get started.
51 |
52 | ::::::::::::::: spoiler
53 |
54 | ## Read me
55 |
56 | Previously, we ran a query on the Gaia server, downloaded data for roughly 140,000
57 | stars, transformed the coordinates to the GD-1 reference frame,
58 | and saved the results in an HDF5 file (Dataset name `results_df`).
59 | We will use that data for this episode.
60 | Whether you are working from a new notebook or coming back from a checkpoint,
61 | reloading the data will save you from having to run the query again.
62 |
63 | If you are starting this episode here or starting this episode in a new notebook,
64 | you will need to run the following lines of code.
65 |
66 | This imports previously imported functions:
67 |
68 | ```python
69 | import astropy.units as u
70 | import matplotlib.pyplot as plt
71 | import pandas as pd
72 |
73 | from episode_functions import *
74 | ```
75 |
76 | The following code loads in the data (instructions for downloading data can be
77 | found in the [setup instructions](../learners/setup.md)). You may need to add a the path
78 | to the filename variable below (e.g. `filename = 'student_download/backup-data/gd1_data.hdf'`)
79 |
80 | ```python
81 | filename = 'gd1_data.hdf'
82 | results_df = pd.read_hdf(filename, 'results_df')
83 | ```
84 |
85 | :::::::::::::::::::::::::
86 |
87 | ::::::::::::::::::::::::::::::::::::::::::::::::::
88 |
89 | ## Exploring data
90 |
91 | One benefit of using Pandas is that it provides functions for
92 | exploring the data and checking for problems.
93 | One of the most useful of these functions is `describe`, which
94 | computes summary statistics for each column.
95 |
96 | ```python
97 | results_df.describe()
98 | ```
99 |
100 | ```output
101 | source_id ra dec pmra \
102 | count 1.403390e+05 140339.000000 140339.000000 140339.000000
103 | mean 6.792399e+17 143.823122 26.780285 -2.484404
104 | std 3.792177e+16 3.697850 3.052592 5.913939
105 | min 6.214900e+17 135.425699 19.286617 -106.755260
106 | 25% 6.443517e+17 140.967966 24.592490 -5.038789
107 | 50% 6.888060e+17 143.734409 26.746261 -1.834943
108 | 75% 6.976579e+17 146.607350 28.990500 0.452893
109 | max 7.974418e+17 152.777393 34.285481 104.319923
110 |
111 | pmdec parallax phi1 phi2 \
112 | [Output truncated]
113 | ```
114 |
115 | ::::::::::::::::::::::::::::::::::::::: challenge
116 |
117 | ## Exercise (10 minutes)
118 |
119 | Review the summary statistics in this table.
120 |
121 | - Do the values make sense based on what you know about the context?
122 |
123 | - Do you see any values that seem problematic, or evidence of other data issues?
124 |
125 | ::::::::::::::: solution
126 |
127 | ## Solution
128 |
129 | The most noticeable issue is that some of the
130 | parallax values are negative, which seems non-physical.
131 |
132 | Negative parallaxes in the Gaia database can arise from a number of
133 | causes like source confusion (high negative values) and the parallax
134 | zero point with systematic errors (low negative values).
135 |
136 | Fortunately, we do not use the parallax measurements in
137 | the analysis (one of the reasons we used constant distance
138 | for reflex correction).
139 |
140 |
141 |
142 | :::::::::::::::::::::::::
143 |
144 | ::::::::::::::::::::::::::::::::::::::::::::::::::
145 |
146 | ## Plot proper motion
147 |
148 | Now we are ready to replicate one of the panels in Figure 1 of the
149 | Price-Whelan and Bonaca paper, the one that shows components of proper
150 | motion as a scatter plot:
151 |
152 |
154 |
155 | In this figure, the shaded area identifies stars that are likely to be
156 | in GD-1 because:
157 |
158 | - Due to the nature of tidal streams, we expect the proper motion for
159 | stars in GD-1 to be along the axis of the stream; that is, we expect
160 | motion in the direction of `phi2` to be near 0.
161 |
162 | - In the direction of `phi1`, we do not have a prior expectation for
163 | proper motion, except that it should form a cluster at a non-zero
164 | value.
165 |
166 | By plotting proper motion in the GD-1 frame, we hope to find this cluster.
167 | Then we will use the bounds of the cluster to select stars that are
168 | more likely to be in GD-1.
169 |
170 | The following figure is a scatter plot of proper motion, in the GD-1
171 | frame, for the stars in `results_df`.
172 |
173 | ```python
174 | x = results_df['pm_phi1']
175 | y = results_df['pm_phi2']
176 | plt.plot(x, y, 'ko', markersize=0.1, alpha=0.1)
177 |
178 | plt.xlabel('Proper motion phi1 (mas/yr GD1 frame)')
179 | plt.ylabel('Proper motion phi2 (mas/yr GD1 frame)')
180 | ```
181 |
182 | ```output
183 |
184 | ```
185 |
186 | {alt='Scatter plot of proper motion in GD-1 frame of selected stars showing most are near the origin.'}
187 |
188 | Most of the proper motions are near the origin, but there are a few
189 | extreme values.
190 | Following the example in the paper, we will use `xlim` and `ylim` to
191 | zoom in on the region near the origin.
192 |
193 | ```python
194 | x = results_df['pm_phi1']
195 | y = results_df['pm_phi2']
196 | plt.plot(x, y, 'ko', markersize=0.1, alpha=0.1)
197 |
198 | plt.xlabel('Proper motion phi1 (mas/yr GD1 frame)')
199 | plt.ylabel('Proper motion phi2 (mas/yr GD1 frame)')
200 |
201 | plt.xlim(-12, 8)
202 | plt.ylim(-10, 10)
203 | ```
204 |
205 | ```output
206 |
207 | ```
208 |
209 | {alt='Zoomed in view of previous scatter plot showing overdense region.'}
210 |
211 | There is a hint of an overdense region near (-7.5, 0), but if you
212 | did not know where to look, you would miss it.
213 |
214 | To see the cluster more clearly, we need a sample that contains a
215 | higher proportion of stars in GD-1.
216 | We will do that by selecting stars close to the centerline.
217 |
218 | ## Selecting the centerline
219 |
220 | As we can see in the following figure, many stars in GD-1 are less
221 | than 1 degree from the line `phi2=0`.
222 |
223 | {alt='Scatter plot with selection on proper motion and photometry showing many stars in GD-1 are within 1 degree of phi2 = 0.'}
224 |
225 | Stars near this line have the highest probability of being in GD-1.
226 |
227 | To select them, we will use a "Boolean mask". We will start by
228 | selecting the `phi2` column from the `DataFrame`:
229 |
230 | ```python
231 | phi2 = results_df['phi2']
232 | type(phi2)
233 | ```
234 |
235 | ```output
236 | pandas.core.series.Series
237 | ```
238 |
239 | The result is a `Series`, which is the structure Pandas uses to
240 | represent columns.
241 |
242 | We can use a comparison operator, `>`, to compare the values in a
243 | `Series` to a constant.
244 |
245 | ```python
246 | phi2_min = -1.0 * u.degree
247 | phi2_max = 1.0 * u.degree
248 |
249 | mask = (phi2 > phi2_min)
250 | type(mask)
251 | ```
252 |
253 | ```output
254 | pandas.core.series.Series
255 | ```
256 |
257 | The result is a `Series` of Boolean values, that is, `True` and `False`.
258 |
259 | ```python
260 | mask.head()
261 | ```
262 |
263 | ```output
264 | 0 False
265 | 1 False
266 | 2 False
267 | 3 False
268 | 4 False
269 | Name: phi2, dtype: bool
270 | ```
271 |
272 | To select values that fall between `phi2_min` and `phi2_max`, we will
273 | use the `&` operator, which computes "logical AND".
274 | The result is true where elements from both Boolean `Series` are true.
275 |
276 | ```python
277 | mask = (phi2 > phi2_min) & (phi2 < phi2_max)
278 | ```
279 |
280 | ::::::::::::::::::::::::::::::::::::::::: callout
281 |
282 | ## Logical operators
283 |
284 | Python's logical operators (`and`, `or`, and `not`)
285 | do not work with NumPy or Pandas. Both libraries use the bitwise
286 | operators (`&`, `|`, and `~`) to do elementwise logical operations
287 | ([explanation here](https://stackoverflow.com/questions/21415661/logical-operators-for-boolean-indexing-in-pandas)).
288 |
289 | Also, we need the parentheses around the conditions; otherwise the
290 | order of operations is incorrect.
291 |
292 |
293 | ::::::::::::::::::::::::::::::::::::::::::::::::::
294 |
295 | The sum of a Boolean `Series` is the number of `True` values, so we
296 | can use `sum` to see how many stars are in the selected region.
297 |
298 | ```python
299 | mask.sum()
300 | ```
301 |
302 | ```output
303 | 25084
304 | ```
305 |
306 | A Boolean `Series` is sometimes called a "mask" because we can use it
307 | to mask out some of the rows in a `DataFrame` and select the rest,
308 | like this:
309 |
310 | ```python
311 | centerline_df = results_df[mask]
312 | type(centerline_df)
313 | ```
314 |
315 | ```output
316 | pandas.core.frame.DataFrame
317 | ```
318 |
319 | `centerline_df` is a `DataFrame` that contains only the rows from
320 | `results_df` that correspond to `True` values in `mask`.
321 | So it contains the stars near the centerline of GD-1.
322 |
323 | We can use `len` to see how many rows are in `centerline_df`:
324 |
325 | ```python
326 | len(centerline_df)
327 | ```
328 |
329 | ```output
330 | 25084
331 | ```
332 |
333 | And what fraction of the rows we have selected.
334 |
335 | ```python
336 | len(centerline_df) / len(results_df)
337 | ```
338 |
339 | ```output
340 | 0.1787386257562046
341 | ```
342 |
343 | There are about 25,000 stars in this region, about 18% of the total.
344 |
345 | ## Plotting proper motion
346 |
347 | This is the second time we are plotting proper motion, and we can imagine we might do it a few more times. Instead of copying
348 | and pasting the previous code, we will write a function that we can reuse on any dataframe.
349 |
350 | ```python
351 | def plot_proper_motion(df):
352 | """Plot proper motion.
353 |
354 | df: DataFrame with `pm_phi1` and `pm_phi2`
355 | """
356 | x = df['pm_phi1']
357 | y = df['pm_phi2']
358 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
359 |
360 | plt.xlabel('Proper motion phi1 (mas/yr)')
361 | plt.ylabel('Proper motion phi2 (mas/yr)')
362 |
363 | plt.xlim(-12, 8)
364 | plt.ylim(-10, 10)
365 | ```
366 |
367 | And we can call it like this:
368 |
369 | ```python
370 | plot_proper_motion(centerline_df)
371 | ```
372 |
373 | ```output
374 |
375 | ```
376 |
377 | {alt='Scatter plot of proper motion of selected stars showing cluster near (-7.5, 0).'}
378 |
379 | Now we can see more clearly that there is a cluster near (-7.5, 0).
380 |
381 | You might notice that our figure is less dense than the one in the
382 | paper. That is because we started with a set of stars from a
383 | relatively small region. The figure in the paper is based on a region
384 | about 10 times bigger.
385 |
386 | In the next episode we will go back and select stars from a larger
387 | region. But first we will use the proper motion data to identify stars
388 | likely to be in GD-1.
389 |
390 | ## Filtering based on proper motion
391 |
392 | The next step is to select stars in the "overdense" region of proper
393 | motion, which are candidates to be in GD-1.
394 |
395 | In the original paper, Price-Whelan and Bonaca used a polygon to cover
396 | this region, as shown in this figure.
397 |
398 |
400 |
401 | We will use a simple rectangle for now, but in a later lesson we will see
402 | how to select a polygonal region as well.
403 |
404 | Here are bounds on proper motion we chose by eye:
405 |
406 | ```python
407 | pm1_min = -8.9
408 | pm1_max = -6.9
409 | pm2_min = -2.2
410 | pm2_max = 1.0
411 | ```
412 |
413 | To draw these bounds, we will use the `make_rectangle` function we wrote in episode 2 to make two lists containing the coordinates of the corners of the rectangle.
414 |
415 | ```python
416 | pm1_rect, pm2_rect = make_rectangle(
417 | pm1_min, pm1_max, pm2_min, pm2_max)
418 | ```
419 |
420 | Here is what the plot looks like with the bounds we chose.
421 |
422 | ```python
423 | plot_proper_motion(centerline_df)
424 | plt.plot(pm1_rect, pm2_rect, '-')
425 | ```
426 |
427 | ```output
428 |
429 | ```
430 |
431 | {alt='Scatter plot of proper motion with blue box showing overdense region selected for our analysis.'}
432 |
433 | Now that we have identified the bounds of the cluster in proper motion,
434 | we will use it to select rows from `results_df`.
435 |
436 | We will use the following function, which uses Pandas operators to make
437 | a mask that selects rows where `series` falls between `low` and
438 | `high`.
439 |
440 | ```python
441 | def between(series, low, high):
442 | """Check whether values are between `low` and `high`."""
443 | return (series > low) & (series < high)
444 | ```
445 |
446 | The following mask selects stars with proper motion in the region we chose.
447 |
448 | ```python
449 | pm1 = results_df['pm_phi1']
450 | pm2 = results_df['pm_phi2']
451 |
452 | pm_mask = (between(pm1, pm1_min, pm1_max) &
453 | between(pm2, pm2_min, pm2_max))
454 | ```
455 |
456 | Again, the sum of a Boolean series is the number of `TRUE` values.
457 |
458 | ```python
459 | pm_mask.sum()
460 | ```
461 |
462 | ```output
463 | 1049
464 | ```
465 |
466 | Now we can use this mask to select rows from `results_df`.
467 |
468 | ```python
469 | selected_df = results_df[pm_mask]
470 | len(selected_df)
471 | ```
472 |
473 | ```output
474 | 1049
475 | ```
476 |
477 | These are the stars we think are likely to be in GD-1. We can
478 | inspect these stars, plotting their coordinates (not their proper motion).
479 |
480 | ```python
481 | x = selected_df['phi1']
482 | y = selected_df['phi2']
483 | plt.plot(x, y, 'ko', markersize=1, alpha=1)
484 |
485 | plt.xlabel('phi1 (degree GD1)')
486 | plt.ylabel('phi2 (degree GD1)')
487 | ```
488 |
489 | ```output
490 |
491 | ```
492 |
493 | {alt='Scatter plot of coordinates of stars in selected region, showing tidal stream.'}
494 |
495 | Now that is starting to look like a tidal stream!
496 |
497 | To clean up the plot a little bit we can add two new Matplotlib commands:
498 |
499 | - `axis` with the parameter `equal` sets up the axes so a unit is the
500 | same size along the `x` and `y` axes.
501 |
502 | - `title` puts the input string as a title at the top of the plot. The `fontsize` keyword
503 | sets the `fontsize` to be `medium`, a little smaller than the default `large`.
504 |
505 | In an example like this, where `x` and `y` represent coordinates in
506 | space, equal axes ensures that the distance between points is
507 | represented accurately. Since we are now constraining the relative proportions
508 | of our axes, the data may not fill the entire figure.
509 |
510 | ```python
511 | x = selected_df['phi1']
512 | y = selected_df['phi2']
513 |
514 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
515 |
516 | plt.xlabel('phi1 [deg]')
517 | plt.ylabel('phi2 [deg]')
518 | plt.title('Proper motion selection', fontsize='medium')
519 |
520 | plt.axis('equal')
521 | ```
522 |
523 | ```output
524 |
525 | ```
526 |
527 | {alt='Scatter plot of coordinates of stars in selected region, showing tidal stream with equally proportioned axes.'}
528 |
529 | Before we go any further, we will put the code we wrote to make one of the panel
530 | figures into a function that we will use in future episodes to recreate this
531 | entire plot with a single line of code.
532 |
533 | ```python
534 | def plot_pm_selection(df):
535 | """Plot in GD-1 spatial coordinates the location of the stars
536 | selected by proper motion
537 | """
538 | x = df['phi1']
539 | y = df['phi2']
540 |
541 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
542 |
543 | plt.xlabel('phi1 [deg]')
544 | plt.ylabel('phi2 [deg]')
545 | plt.title('Proper motion selection', fontsize='medium')
546 |
547 | plt.axis('equal')
548 | ```
549 |
550 | Now our one line plot command is:
551 |
552 | ```python
553 | plot_pm_selection(selected_df)
554 | ```
555 |
556 | ## Saving the DataFrame
557 |
558 | At this point we have run a successful query and cleaned up the
559 | results. This is a good time to save the data.
560 | We have already started a results file called gd1\_data.hdf which we wrote `results_df` to.
561 |
562 | Recall that we chose HDF5 because it is a binary format producing small files that are fast to read and write and are a cross-language standard.
563 |
564 | Additionally, HDF5 files can contain more than one dataset and can store metadata associated with each dataset (such as column names or observatory information, like a FITS header).
565 |
566 | We can add to our existing Pandas `DataFrame` to an HDF5 file by omitting the `mode='w'` keyword like this:
567 |
568 | ```python
569 | filename = 'gd1_data.hdf'
570 |
571 | selected_df.to_hdf(filename, 'selected_df')
572 | ```
573 |
574 | Because an HDF5 file can contain more than one Dataset, we have to
575 | provide a name, or "key", that identifies the Dataset in the file.
576 |
577 | We could use any string as the key, but it is generally a good practice
578 | to use a descriptive name (just like your `DataFrame` variable name) so
579 | we will give the Dataset in the file the same name (key) as the `DataFrame`.
580 |
581 | ::::::::::::::::::::::::::::::::::::::: challenge
582 |
583 | ## Exercise (5 minutes)
584 |
585 | We are going to need `centerline_df` later as well. Write a line of
586 | code to add it as a second Dataset in the HDF5 file.
587 |
588 | Hint: Since the file already exists, you should *not* use `mode='w'`.
589 |
590 | ::::::::::::::: solution
591 |
592 | ## Solution
593 |
594 | ```python
595 | centerline_df.to_hdf(filename, 'centerline_df')
596 | ```
597 |
598 | :::::::::::::::::::::::::
599 |
600 | ::::::::::::::::::::::::::::::::::::::::::::::::::
601 |
602 | We can use `getsize` to confirm that the file exists and check the size.
603 | `getsize` returns a value in bytes. For the size files we're looking at, it will
604 | be useful to view their size in MegaBytes (MB), so we will divide by 1024\*1024.
605 |
606 | ```python
607 | from os.path import getsize
608 |
609 | MB = 1024 * 1024
610 | getsize(filename) / MB
611 | ```
612 |
613 | ```output
614 | 13.992530822753906
615 | ```
616 |
617 | If you forget what the names of the Datasets in the file are, you can
618 | read them back like this:
619 |
620 | ```python
621 | with pd.HDFStore(filename) as hdf:
622 | print(hdf.keys())
623 | ```
624 |
625 | ```output
626 | ['/centerline_df', '/results_df', '/selected_df']
627 | ```
628 |
629 | ::::::::::::::::::::::::::::::::::::::::: callout
630 |
631 | ## Context Managers
632 |
633 | We use a `with` statement here to open the file
634 | before the print statement and (automatically) close it after. Read
635 | more about [context managers](https://book.pythontips.com/en/latest/context_managers.html).
636 |
637 |
638 | ::::::::::::::::::::::::::::::::::::::::::::::::::
639 |
640 | The keys are the names of the Datasets which makes it easy for us to remember which `DataFrame` is
641 | in which Dataset.
642 |
643 | ## Summary
644 |
645 | In this episode, we re-loaded the transformed Gaia data we saved from a previous query.
646 |
647 | Then we prototyped the selection process from the Price-Whelan and Bonaca paper locally using data that we had already downloaded.:
648 |
649 | - We selected stars near the centerline of GD-1 and made a scatter
650 | plot of their proper motion.
651 |
652 | - We identified a region of proper motion that contains stars likely
653 | to be in GD-1.
654 |
655 | - We used a Boolean `Series` as a mask to select stars whose proper
656 | motion is in that region.
657 |
658 | So far, we have used data from a relatively small region of the sky so that our local dataset was not too big.
659 | In the next lesson, we will write a query that selects stars based on
660 | the proper motion limits we identified in this lesson, which will allow us to explore a larger region.
661 |
662 | :::::::::::::::::::::::::::::::::::::::: keypoints
663 |
664 | - A workflow is often prototyped on a small set of data which can be explored more easily and used to identify ways to limit a dataset to exactly the data you want.
665 | - To store data from a Pandas `DataFrame`, a good option is an HDF5 file, which can contain multiple Datasets.
666 |
667 | ::::::::::::::::::::::::::::::::::::::::::::::::::
668 |
669 |
670 |
--------------------------------------------------------------------------------
/episodes/05-select.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Transform and Select
3 | teaching: 55
4 | exercises: 15
5 | ---
6 |
7 | ::::::::::::::::::::::::::::::::::::::: objectives
8 |
9 | - Transform proper motions from one frame to another.
10 | - Compute the convex hull of a set of points.
11 | - Write an ADQL query that selects based on proper motion.
12 |
13 | ::::::::::::::::::::::::::::::::::::::::::::::::::
14 |
15 | :::::::::::::::::::::::::::::::::::::::: questions
16 |
17 | - When should we use the database server for computation?
18 | - When should we download the data from the database server and compute locally?
19 |
20 | ::::::::::::::::::::::::::::::::::::::::::::::::::
21 |
22 |
23 |
24 | In the previous episode, we identified stars with the proper motion we
25 | expect for GD-1.
26 |
27 | Now we will do the same selection in an ADQL query, which will make it
28 | possible to work with a larger region of the sky and still download
29 | less data.
30 |
31 | ::::::::::::::::::::::::::::::::::::::: checklist
32 |
33 | ## Outline
34 |
35 | 1. Using data from the previous episode, we will identify the values of
36 | proper motion for stars likely to be in GD-1.
37 |
38 | 2. Then we will compose an ADQL query that selects stars based on proper
39 | motion, so we can download only the data we need.
40 |
41 | That will make it possible to search a bigger region of the sky in a
42 | single query.
43 |
44 |
45 | ::::::::::::::::::::::::::::::::::::::::::::::::::
46 |
47 | :::::::::::::::::::::::::::::::::::::::::: prereq
48 |
49 | ## Starting from this episode
50 |
51 | If you are starting a new notebook for this episode, expand this section
52 | for information you will need to get started.
53 |
54 | ::::::::::::::: spoiler
55 |
56 | ## Read me
57 |
58 | Previously, we ran a query on the Gaia server, downloaded data for roughly 140,000 stars,
59 | and saved the data in a FITS file.
60 | We then selected just the stars with the same proper motion as GD-1 and saved
61 | the results to an HDF5 file.
62 | We will use that data for this episode.
63 | Whether you are working from a new notebook or coming back from a checkpoint,
64 | reloading the data will save you from having to run the query again.
65 |
66 | If you are starting this episode here or starting this episode in a new notebook,
67 | you will need to run the following lines of code.
68 |
69 | This imports previously imported functions:
70 |
71 | ```python
72 | import astropy.units as u
73 | from astropy.coordinates import SkyCoord
74 | from astroquery.gaia import Gaia
75 | from gala.coordinates import GD1Koposov10, GD1, reflex_correct
76 | import matplotlib.pyplot as plt
77 | import pandas as pd
78 |
79 | from episode_functions import *
80 | ```
81 |
82 | The following code loads in the data (instructions for downloading data can be
83 | found in the [setup instructions](../learners/setup.md)). You may need to add a the path
84 | to the filename variable below (e.g. `filename = 'student_download/backup-data/gd1_data.hdf'`)
85 |
86 | ```python
87 | filename = 'gd1_data.hdf'
88 | centerline_df = pd.read_hdf(filename, 'centerline_df')
89 | selected_df = pd.read_hdf(filename, 'selected_df')
90 | ```
91 |
92 | This defines previously defined quantities:
93 |
94 | ```python
95 | pm1_min = -8.9
96 | pm1_max = -6.9
97 | pm2_min = -2.2
98 | pm2_max = 1.0
99 |
100 | pm1_rect, pm2_rect = make_rectangle(
101 | pm1_min, pm1_max, pm2_min, pm2_max)
102 | gd1_frame = GD1Koposov10()
103 | ```
104 |
105 | :::::::::::::::::::::::::
106 |
107 | ::::::::::::::::::::::::::::::::::::::::::::::::::
108 |
109 | ## Selection by proper motion
110 |
111 | Let us review how we got to this point.
112 |
113 | 1. We made an ADQL query to the Gaia server to get data for stars in
114 | the vicinity of a small part of GD-1.
115 |
116 | 2. We transformed the coordinates to the GD-1 frame (`GD1Koposov10`) so we
117 | could select stars along the centerline of GD-1.
118 |
119 | 3. We plotted the proper motion of stars along the centerline of GD-1
120 | to identify the bounds of an anomalous overdense region associated
121 | with the proper motion of stars in GD-1.
122 |
123 | 4. We made a mask that selects stars whose proper motion is in this
124 | overdense region and which are therefore likely to be part of the GD-1 stream.
125 |
126 | At this point we have downloaded data for a relatively large number of
127 | stars (more than 100,000) and selected a relatively small number
128 | (around 1000).
129 |
130 | It would be more efficient to use ADQL to select only the stars we
131 | need. That would also make it possible to download data covering a
132 | larger region of the sky.
133 |
134 | However, the selection we did was based on proper motion in the
135 | GD-1 frame. In order to do the same selection on the Gaia catalog in ADQL,
136 | we have to work with proper motions in the ICRS frame as this is the
137 | frame that the Gaia catalog uses.
138 |
139 | First, we will verify that our proper motion selection was correct,
140 | starting with the `plot_proper_motion` function that we defined in episode 3.
141 | The following figure shows:
142 |
143 | - Proper motion for the stars we selected along the center line of GD-1,
144 |
145 | - The rectangle we selected, and
146 |
147 | - The stars inside the rectangle highlighted in green.
148 |
149 | ```python
150 | plot_proper_motion(centerline_df)
151 |
152 | plt.plot(pm1_rect, pm2_rect)
153 |
154 | x = selected_df['pm_phi1']
155 | y = selected_df['pm_phi2']
156 | plt.plot(x, y, 'gx', markersize=0.3, alpha=0.3);
157 | ```
158 |
159 | ```output
160 |
161 | ```
162 |
163 | {alt='Proper motion of stars in GD-1, showing selected region as blue box and stars within selection as green points.'}
164 |
165 | Now we will make the same plot using proper motions in the ICRS frame,
166 | which are stored in columns named `pmra` and `pmdec`.
167 |
168 | ```python
169 | x = centerline_df['pmra']
170 | y = centerline_df['pmdec']
171 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
172 |
173 | x = selected_df['pmra']
174 | y = selected_df['pmdec']
175 | plt.plot(x, y, 'gx', markersize=1, alpha=0.3)
176 |
177 | plt.xlabel('Proper motion ra (ICRS frame)')
178 | plt.ylabel('Proper motion dec (ICRS frame)')
179 |
180 | plt.xlim([-10, 5])
181 | plt.ylim([-20, 5]);
182 | ```
183 |
184 | ```output
185 |
186 | ```
187 |
188 | {alt='Proper motion in ICRS frame, showing selected stars are more spread out in this frame.'}
189 |
190 | The proper motions of the selected stars are more spread out in this
191 | frame, which is why it was preferable to do the selection in the GD-1
192 | frame.
193 |
194 | In the following exercise, we will identify a rectangle that encompasses the majority of the stars we identified as having proper motion consistent with that of GD-1 without including too many other stars.
195 |
196 | ::::::::::::::::::::::::::::::::::::::: challenge
197 |
198 | ## Exercise (5 minutes)
199 |
200 | Looking at the proper motion of the stars we identified along the centerline of GD-1, in the ICRS reference frame
201 | define a rectangle (`pmra_min`, `pmra_max`, `pmdec_min`, and `pmdec_max`)
202 | that encompass the proper motion of the majority of the stars near the centerline of GD-1 without including to much contamination from other stars.
203 |
204 | ::::::::::::::: solution
205 |
206 | ## Solution
207 |
208 | ```python
209 | pmra_min = -6.70
210 | pmra_max = -3
211 | pmdec_min = -14.31
212 | pmdec_max = -11.2
213 | ```
214 |
215 | :::::::::::::::::::::::::
216 |
217 | ::::::::::::::::::::::::::::::::::::::::::::::::::
218 |
219 | ## Assembling the query
220 |
221 | In episode 2 we used the following query to select stars in a polygonal region
222 | around a small part of GD-1 with a few filters on color and distance (parallax):
223 |
224 | ```python
225 | candidate_coord_query_base = """SELECT
226 | {columns}
227 | FROM gaiadr2.gaia_source
228 | WHERE parallax < 1
229 | AND bp_rp BETWEEN -0.75 AND 2
230 | AND 1 = CONTAINS(POINT(ra, dec),
231 | POLYGON({sky_point_list}))
232 | """
233 | ```
234 |
235 | In this episode we will make two changes:
236 |
237 | 1. We will select stars with coordinates in a larger region to include more of GD-1.
238 |
239 | 2. We will add another clause to select stars whose proper motion is in
240 | the range we just defined in the previous exercise.
241 |
242 | The fact that we remove most contaminating stars with the proper
243 | motion filter is what allows us to expand our query to include
244 | most of GD-1 without returning too many results.
245 | As we did in episode 2, we will define the physical region we want
246 | to select in the GD-1 frame and transform it to the ICRS frame
247 | to query the Gaia catalog which is in the ICRS frame.
248 |
249 | Here are the coordinates of the larger rectangle in the GD-1 frame.
250 |
251 | ```python
252 | phi1_min = -70 * u.degree
253 | phi1_max = -20 * u.degree
254 | phi2_min = -5 * u.degree
255 | phi2_max = 5 * u.degree
256 | ```
257 |
258 | We selected these bounds by trial and error, defining the largest
259 | region we can process in a single query.
260 |
261 | ```python
262 | phi1_rect, phi2_rect = make_rectangle(
263 | phi1_min, phi1_max, phi2_min, phi2_max)
264 | ```
265 |
266 | Here is how we transform it to ICRS, as we saw in episode 2.
267 |
268 | ```python
269 | corners = SkyCoord(phi1=phi1_rect,
270 | phi2=phi2_rect,
271 | frame=gd1_frame)
272 |
273 | corners_icrs = corners.transform_to('icrs')
274 | ```
275 |
276 | To use `corners_icrs` as part of an ADQL query, we have to convert it
277 | to a string.
278 | Fortunately, we wrote a function, `skycoord_to_string` to do this in episode 2
279 | which we will call now.
280 |
281 | ```python
282 | sky_point_list = skycoord_to_string(corners_icrs)
283 | sky_point_list
284 | ```
285 |
286 | ```output
287 | '135.306, 8.39862, 126.51, 13.4449, 163.017, 54.2424, 172.933, 46.4726, 135.306, 8.39862'
288 | ```
289 |
290 | Here are the columns we want to select.
291 |
292 | ```python
293 | columns = 'source_id, ra, dec, pmra, pmdec'
294 | ```
295 |
296 | Now we have everything we need to assemble the query, but
297 | **DO NOT try to run this query**.
298 | Because it selects a larger region, there are too many stars to handle
299 | in a single query. Until we select by proper motion, that is.
300 |
301 | ```python
302 | candidate_coord_query = candidate_coord_query_base.format(columns=columns,
303 | sky_point_list=sky_point_list)
304 | print(candidate_coord_query)
305 | ```
306 |
307 | ```output
308 | SELECT
309 | source_id, ra, dec, pmra, pmdec
310 | FROM gaiadr2.gaia_source
311 | WHERE parallax < 1
312 | AND bp_rp BETWEEN -0.75 AND 2
313 | AND 1 = CONTAINS(POINT(ra, dec),
314 | POLYGON(135.306, 8.39862, 126.51, 13.4449, 163.017, 54.2424, 172.933, 46.4726, 135.306, 8.39862))
315 | ```
316 |
317 | ## Selecting proper motion
318 |
319 | Now we are ready to add a `WHERE` clause to select stars whose proper
320 | motion falls range we defined in the last exercise.
321 |
322 | ::::::::::::::::::::::::::::::::::::::: challenge
323 |
324 | ## Exercise (10 minutes)
325 |
326 | Define `candidate_coord_pm_query_base`, starting with `candidate_coord_query_base` and adding two new `BETWEEN`
327 | clauses to select stars whose coordinates of proper motion, `pmra` and
328 | `pmdec`, fall within the region defined by `pmra_min`, `pmra_max`, `pmdec_min`, and `pmdec_max`.
329 | In the next exercise we will use the format statement to fill in the values we defined above.
330 |
331 | ::::::::::::::: solution
332 |
333 | ## Solution
334 |
335 | ```python
336 | candidate_coord_pm_query_base = """SELECT
337 | {columns}
338 | FROM gaiadr2.gaia_source
339 | WHERE parallax < 1
340 | AND bp_rp BETWEEN -0.75 AND 2
341 | AND 1 = CONTAINS(POINT(ra, dec),
342 | POLYGON({sky_point_list}))
343 | AND pmra BETWEEN {pmra_min} AND {pmra_max}
344 | AND pmdec BETWEEN {pmdec_min} AND {pmdec_max}
345 | """
346 | ```
347 |
348 | :::::::::::::::::::::::::
349 |
350 | ::::::::::::::::::::::::::::::::::::::::::::::::::
351 |
352 | ::::::::::::::::::::::::::::::::::::::: challenge
353 |
354 | ## Exercise (5 minutes)
355 |
356 | Use `format` to format `candidate_coord_pm_query_base` and define `candidate_coord_pm_query`, filling in
357 | the values of `columns`, `sky_point_list`, and `pmra_min`, `pmra_max`, `pmdec_min`, `pmdec_max`.
358 |
359 | ::::::::::::::: solution
360 |
361 | ## Solution
362 |
363 | ```python
364 | candidate_coord_pm_query = candidate_coord_pm_query_base.format(columns=columns,
365 | sky_point_list=sky_point_list,
366 | pmra_min=pmra_min,
367 | pmra_max=pmra_max,
368 | pmdec_min=pmdec_min,
369 | pmdec_max=pmdec_max)
370 | print(candidate_coord_pm_query)
371 | ```
372 |
373 | :::::::::::::::::::::::::
374 |
375 | ::::::::::::::::::::::::::::::::::::::::::::::::::
376 |
377 | Now we can run the query like this:
378 |
379 | ```python
380 | candidate_coord_pm_job = Gaia.launch_job_async(candidate_coord_pm_query)
381 | print(candidate_coord_pm_job)
382 | ```
383 |
384 | ```output
385 | INFO: Query finished. [astroquery.utils.tap.core]
386 |
387 | name dtype unit description
388 | --------- ------- -------- ------------------------------------------------------------------
389 | source_id int64 Unique source identifier (unique within a particular Data Release)
390 | ra float64 deg Right ascension
391 | dec float64 deg Declination
392 | pmra float64 mas / yr Proper motion in right ascension direction
393 | pmdec float64 mas / yr Proper motion in declination direction
394 | Jobid: 1616771462206O
395 | Phase: COMPLETED
396 | [Output truncated]
397 | ```
398 |
399 | And get the results.
400 |
401 | ```python
402 | candidate_gaia_table = candidate_coord_pm_job.get_results()
403 | len(candidate_gaia_table)
404 | ```
405 |
406 | ```output
407 | 8409
408 | ```
409 |
410 | ::::::::::::::::::::::::::::::::::::::::: callout
411 |
412 | ## BETWEEN vs POLYGON
413 |
414 | You may be wondering why we used `BETWEEN` for proper motion when we previously used `POLYGON`
415 | for coordinates. ADQL intends the `POLYGON` function to only be used on coordinates and not on proper motion.
416 | To enforce this, it will produce an error when a negative value is passed into the first argument.
417 |
418 |
419 | ::::::::::::::::::::::::::::::::::::::::::::::::::
420 |
421 | We call the results `candidate_gaia_table` because it contains information from
422 | the Gaia table for stars that are good candidates for GD-1.
423 |
424 | `sky_point_list`, `pmra_min`, `pmra_max`, `pmdec_min`, and `pmdec_max` are a set of selection criteria that we
425 | derived from data downloaded from the Gaia Database. To make sure we can repeat
426 | our analysis at a later date we should save this information to a file.
427 |
428 | There are several ways we could do that, but since we are already
429 | storing data in an HDF5 file, we will do the same with these variables.
430 |
431 | To save them to an HDF5 file we first need to put them in a Pandas object.
432 | We have seen how to create a `Series` from a column in a `DataFrame`.
433 | Now we will build a `Series` from scratch.
434 | We do not need the full `DataFrame` format with multiple rows and columns
435 | because we are only storing one string (`sky_point_list`).
436 | We can store each string as a row in the `Series` and save it. One aspect that
437 | is nice about `Series` is that we can label each row.
438 | To do this we need an object that can define both the name of each row and
439 | the data to go in that row. We can use a Python `Dictionary` for this,
440 | defining the row names with the dictionary keys and the row data with
441 | the dictionary values.
442 |
443 | ```python
444 | d = dict(sky_point_list=sky_point_list, pmra_min=pmra_min, pmra_max=pmra_max, pmdec_min=pmdec_min, pmdec_max=pmdec_max)
445 | d
446 | ```
447 |
448 | ```output
449 | {'sky_point_list': '135.306, 8.39862, 126.51, 13.4449, 163.017, 54.2424, 172.933, 46.4726, 135.306, 8.39862',
450 | 'pmra_min': -6.7,
451 | 'pmra_max': -3,
452 | 'pmdec_min': -14.31,
453 | 'pmdec_max': -11.2}
454 | ```
455 |
456 | And use this `Dictionary` to initialize a `Series`.
457 |
458 | ```python
459 | point_series = pd.Series(d)
460 | point_series
461 | ```
462 |
463 | ```output
464 | sky_point_list 135.306, 8.39862, 126.51, 13.4449, 163.017, 54...
465 | pmra_min -6.7
466 | pmra_max -3
467 | pmdec_min -14.31
468 | pmdec_max -11.2
469 | dtype: object
470 | ```
471 |
472 | Now we can save our `Series` using `to_hdf()`.
473 |
474 | ```python
475 | filename = 'gd1_data.hdf'
476 | point_series.to_hdf(filename, 'point_series')
477 | ```
478 |
479 | ::::::::::::::::::::::::::::::::::::::::: callout
480 |
481 | ## Performance Warning
482 |
483 | You may see the previous command issue this or a similar performance warning:
484 |
485 | ```output
486 | [...] PerformanceWarning:
487 | your performance may suffer as PyTables will pickle object types that it cannot
488 | map directly to c-types [inferred_type->mixed-integer,key->values] [items->None]
489 | point_series.to_hdf(filename, 'point_series')
490 | ```
491 |
492 | This is because in the Series we just created, we are mixing variables of
493 | different types: `sky_point_list` is a string (text), whereas `pmra_min`
494 | etc. are floating point numbers. While combining different data types in a
495 | single Series is somewhat inefficient, the amount of data is small enough to
496 | not matter in this case, so this warning can be safely ignored.
497 |
498 |
499 | ::::::::::::::::::::::::::::::::::::::::::::::::::
500 |
501 | ## Plotting one more time
502 |
503 | Now we can examine the results:
504 |
505 | ```python
506 | x = candidate_gaia_table['ra']
507 | y = candidate_gaia_table['dec']
508 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
509 |
510 | plt.xlabel('ra (degree ICRS)')
511 | plt.ylabel('dec (degree ICRS)');
512 | ```
513 |
514 | ```output
515 |
516 | ```
517 |
518 | {alt='Scatter plot of right ascension and declination of selected stars in ICRS frame.'}
519 |
520 | This plot shows why it was useful to transform these coordinates to the GD-1 frame. In
521 | ICRS, it is more difficult to identity the stars near the centerline
522 | of GD-1.
523 |
524 | We can use our `make_dataframe` function from episode 3 to transform the results back
525 | to the GD-1 frame. In addition to doing the coordinate transformation and reflex correction
526 | for us, this function also compiles everything into a single object (a `DataFrame`) to make it easier to use. Note that because we put this code into a function, we can do all of this with a single line of code!
527 |
528 | ```python
529 | candidate_gaia_df = make_dataframe(candidate_gaia_table)
530 | ```
531 |
532 | We can check the results using the `plot_pm_selection` function we wrote in episode 3.
533 |
534 | ```python
535 | plot_pm_selection(candidate_gaia_df)
536 | ```
537 |
538 | ```output
539 |
540 | ```
541 |
542 | {alt='Scatter plot of phi1 versus phi2 in GD-1 frame after selecting on proper motion.'}
543 |
544 | We are starting to see GD-1 more clearly.
545 | We can compare this figure with this panel from Figure 1 from the
546 | original paper:
547 |
548 |
550 |
551 | This panel shows stars selected based on proper motion only, so it is
552 | comparable to our figure (although notice that the original figure covers a wider
553 | region).
554 |
555 | In the next episode, we will use photometry data from Pan-STARRS to do
556 | a second round of filtering, and see if we can replicate this panel.
557 |
558 |
560 |
561 | Later we will learn how to add annotations like the ones in the figure and
562 | customize the style of the figure to present the results clearly and
563 | compellingly.
564 |
565 | ## Summary
566 |
567 | In the previous episode we downloaded data for a large number of stars
568 | and then selected a small fraction of them based on proper motion.
569 |
570 | In this episode, we improved this process by writing a more complex
571 | query that uses the database to select stars based on proper motion.
572 | This process requires more computation on the Gaia server, but then
573 | we are able to either:
574 |
575 | 1. Search the same region and download less data, or
576 |
577 | 2. Search a larger region while still downloading a manageable amount of data.
578 |
579 | In the next episode, we will learn about the database `JOIN` operation, which we will use
580 | in later episodes to join our Gaia data with photometry data from Pan-STARRS.
581 |
582 | :::::::::::::::::::::::::::::::::::::::: keypoints
583 |
584 | - When possible, 'move the computation to the data'; that is, do as much of the work as possible on the database server before downloading the data.
585 |
586 | ::::::::::::::::::::::::::::::::::::::::::::::::::
587 |
588 |
589 |
--------------------------------------------------------------------------------
/episodes/07-photo.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Photometry
3 | teaching: 30
4 | exercises: 10
5 | ---
6 |
7 | ::::::::::::::::::::::::::::::::::::::: objectives
8 |
9 | - Use isochrone data to specify a polygon and determine which points fall inside it.
10 | - Use Matplotlib features to customize the appearance of figures.
11 |
12 | ::::::::::::::::::::::::::::::::::::::::::::::::::
13 |
14 | :::::::::::::::::::::::::::::::::::::::: questions
15 |
16 | - How do we use Matplotlib to define a polygon and select points that fall inside it?
17 |
18 | ::::::::::::::::::::::::::::::::::::::::::::::::::
19 |
20 |
21 |
22 | In the previous episode we downloaded photometry data from Pan-STARRS,
23 | which is available from the same server we have been using to get Gaia
24 | data.
25 |
26 | The next step in the analysis is to select candidate stars based on
27 | the photometry data.
28 | The following figure from the paper is a color-magnitude diagram
29 | showing the stars we previously selected based on proper motion:
30 |
31 |
33 |
34 | In red is a [theoretical isochrone](https://en.wikipedia.org/wiki/Stellar_isochrone), showing where we expect the stars
35 | in GD-1 to fall based on the metallicity and age of their original
36 | globular cluster.
37 |
38 | By selecting stars in the shaded area, we can further distinguish the
39 | main sequence of GD-1 from mostly younger background stars.
40 |
41 | ::::::::::::::::::::::::::::::::::::::: checklist
42 |
43 | ## Outline
44 |
45 | 1. We will reload the data from the previous episode and make a
46 | color-magnitude diagram.
47 |
48 | 2. We will use an isochrone computed by MIST to specify a polygonal
49 | region in the color-magnitude diagram and select the stars inside of it.
50 |
51 |
52 | ::::::::::::::::::::::::::::::::::::::::::::::::::
53 |
54 | :::::::::::::::::::::::::::::::::::::::::: prereq
55 |
56 | ## Starting from this episode
57 |
58 | If you are starting a new notebook for this episode, expand this section
59 | for information you will need to get started.
60 |
61 | ::::::::::::::: spoiler
62 |
63 | ## Read me
64 |
65 | In the previous episode, we selected stars in GD-1 based on proper motion and downloaded
66 | the spatial, proper motion, and photometry information by joining the Gaia and PanSTARRs
67 | datasets.
68 | We will use that data for this episode.
69 | Whether you are working from a new notebook or coming back from a checkpoint,
70 | reloading the data will save you from having to run the query again.
71 |
72 | If you are starting this episode here or starting this episode in a new notebook,
73 | you will need run the following lines of code.
74 |
75 | This imports previously imported functions:
76 |
77 | ```python
78 | from os.path import getsize
79 |
80 | import pandas as pd
81 |
82 | from matplotlib import pyplot as plt
83 |
84 | from episode_functions import *
85 | ```
86 |
87 | The following code loads in the data (instructions for downloading data can be
88 | found in the [setup instructions](../learners/setup.md)). You may need to add a the path
89 | to the filename variable below (e.g. `filename = 'student_download/backup-data/gd1_data.hdf'`)
90 |
91 | ```python
92 | filename = 'gd1_data.hdf'
93 | candidate_df = pd.read_hdf(filename, 'candidate_df')
94 | ```
95 |
96 | :::::::::::::::::::::::::
97 |
98 | ::::::::::::::::::::::::::::::::::::::::::::::::::
99 |
100 | ## Plotting photometry data
101 |
102 | Now that we have photometry data from Pan-STARRS, we can produce a
103 | [color-magnitude
104 | diagram](https://coolwiki.ipac.caltech.edu/index.php/Color-Magnitude_and_Color-Color_plots_Overview)
105 | to replicate the diagram from the original paper:
106 |
107 |
109 |
110 | The y-axis shows the apparent magnitude of each source with the [g
111 | filter](https://en.wikipedia.org/wiki/Photometric_system).
112 |
113 | The x-axis shows the difference in apparent magnitude between the g
114 | and i filters, which indicates color.
115 |
116 | :::::::::::::::::::::::::::::::::::::: discussion
117 |
118 | ## A slight detour for non-domain experts
119 |
120 | If you are wondering (as a non-domain expert)
121 | how to interpret this color-magnitude diagram, please expand the section below.
122 |
123 | ::::::::::::::: spoiler
124 |
125 | ## The color magnitude diagram of GD-1 and foreground stars
126 |
127 | As a pathologist can easily point to tumor on a biopsy slide, so too can astronomers who study stellar populations see two stellar main groups of stars in this color magnitude diagram, one from an old star cluster (presumably, GD-1), and the other, stars much closer, but at every distance between the Earth and the cluster ("foreground"). The color magnitude diagram is a technique developed to separate these features out just as pathologists have techniques to contrast human tissue.
128 | The color of a star is primarily related to the star's surface temperature, with bluer stars indicating higher temperatures and redder stars indicating lower temperatures. This logic is not too far off from the color at the bottom of a match flame compared to the top.
129 |
130 | Foreground Stars:
131 | To know the temperature of a star, you first need to know its distance and to account for the dust between us and the star. You can guess the effect of distance. A star farther away will be fainter (lower y-axis value) than the same star closer (think of car headlights as they approach). Dust will remove light from the star's path to our telescopes, which makes the star seem like it has less energy than it otherwise would have, which makes it do two things on this diagram: 1) look fainter (lower on the y-axis; larger magnitude value) and 2) look cooler (higher x-axis value). The stars spread throughout the diagram are all stars bright enough to be detected in our Milky Way between GD-1 and us but made fainter and redder (spread to the lower-right) by their spread in distance from us and the amount dust in the line of sight.
132 |
133 | GD-1:
134 | The pile up of stars in the lower-left quadrant of this diagram are interesting because it suggests something is at the same distance with the same amount of dust in the way. When we use our knowledge of theoretical astrophysics (independently calculated outside this work) to estimate how bright a population of old stars would be if it were at the distance of GD-1, we get that solid red line. The exact values of age and metallicity ([Fe/H] value) is a variable needed to reproduce the theoretical isochrone, but frankly, the choice could vary a lot and still would fit the data well.
135 |
136 | [More on color-magnitude diagrams and their theoretical counterpart, here.](https://spiff.rit.edu/classes/ladder/lectures/ordinary_stars/ordinary.html)
137 |
138 |
139 |
140 | :::::::::::::::::::::::::
141 |
142 | ::::::::::::::::::::::::::::::::::::::::::::::::::
143 |
144 | With the photometry we downloaded from the PanSTARRS table into
145 | `candidate_df` we can now recreate this plot.
146 |
147 | ```python
148 | x = candidate_df['g_mean_psf_mag'] - candidate_df['i_mean_psf_mag']
149 | y = candidate_df['g_mean_psf_mag']
150 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
151 |
152 | plt.ylabel('Magnitude (g)')
153 | plt.xlabel('Color (g-i)')
154 | ```
155 |
156 | {alt='Color magnitude diagram of our selected stars showing all of the stars selected'}
157 |
158 | We have assigned the color and magnitude to variables `x` and `y`, respectively.
159 | We have done this out of convenience and to keep the code readable since the
160 | table variables and column names are long and `x` includes an operation
161 | between two columns.
162 |
163 | We can zoom in on the region of interest by setting the range of
164 | x and y values displayed with the `xlim` and `ylim` functions.
165 | If we put the higher value first in the `ylim` call, this will invert
166 | the y-axis, putting fainter magnitudes at the bottom.
167 |
168 | ```python
169 | x = candidate_df['g_mean_psf_mag'] - candidate_df['i_mean_psf_mag']
170 | y = candidate_df['g_mean_psf_mag']
171 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
172 |
173 | plt.ylabel('Magnitude (g)')
174 | plt.xlabel('Color (g-i)')
175 |
176 | plt.xlim([0, 1.5])
177 | plt.ylim([22, 14])
178 | ```
179 |
180 | {alt='Color magnitude diagram of our selected stars showing overdense region in lower left.'}
181 |
182 | Our figure does not look exactly like the one in the paper because we
183 | are working with a smaller region of the sky, so we have fewer
184 | stars. But the main sequence of GD-1 appears as an overdense region in the lower left.
185 |
186 | We want to be able to make this plot again, with any selection of PanSTARRs photometry,
187 | so this is a natural time to put it into a function that accepts as input
188 | an Astropy `Table` or Pandas `DataFrame`, as long as
189 | it has columns named `g_mean_psf_mag` and `i_mean_psf_mag`. To do this we will change
190 | our variable name from `candidate_df` to the more generic `dataframe`.
191 |
192 | ```python
193 | def plot_cmd(dataframe):
194 | """Plot a color magnitude diagram.
195 |
196 | dataframe: DataFrame or Table with photometry data
197 | """
198 | y = dataframe['g_mean_psf_mag']
199 | x = dataframe['g_mean_psf_mag'] - dataframe['i_mean_psf_mag']
200 |
201 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
202 |
203 | plt.xlim([0, 1.5])
204 | plt.ylim([22, 14])
205 |
206 | plt.ylabel('Magnitude (g)')
207 | plt.xlabel('Color (g-i)')
208 | ```
209 |
210 | Here are the results:
211 |
212 | ```python
213 | plot_cmd(candidate_df)
214 | ```
215 |
216 | ```output
217 |
218 | ```
219 |
220 | {alt='Color magnitude diagram of our selected stars showing overdense region in lower left.'}
221 |
222 | In the next section we will use an isochrone to specify a polygon that
223 | contains this overdense region.
224 |
225 | ## Isochrone
226 |
227 | Given our understanding of the age, metallicity, and distance to GD-1 we can overlay a
228 | theoretical isochrone for GD-1 from the MESA Isochrones and Stellar Tracks and better identify the main sequence of GD-1.
229 |
230 | ::::::::::::::::::::::::::::::::::::::::: callout
231 |
232 | ## Calculating Isochrone
233 |
234 | In fact, we can use [MESA Isochrones \& Stellar Tracks](https://waps.cfa.harvard.edu/MIST/) (MIST)
235 | to compute it for us.
236 | Using the [MIST Version 1.2 web interface](https://waps.cfa.harvard.edu/MIST/interp_isos.html),
237 | we computed an isochrone with the following parameters:
238 |
239 | - Rotation initial v/v\_crit = 0.4
240 | - Single age, linear scale = 12e9
241 | - Composition [Fe/H] = -1.35
242 | - Synthetic Photometry, PanStarrs
243 | - Extinction av = 0
244 |
245 |
246 | ::::::::::::::::::::::::::::::::::::::::::::::::::
247 |
248 | ## Making a polygon
249 |
250 | The MIST isochrone files available on the website above can not be directly plotted over our data.
251 | We have selected the relevant part of the isochrone, the filters we are interested in, and scaled the photometry to the distance of GD-1
252 | ([details here](../instructors/calculating_MIST_isochrone.md)).
253 | Now we can read in the results which you downloaded as part of the [setup instructions](../learners/setup.md):
254 |
255 | ```python
256 | filename = 'gd1_isochrone.hdf5'
257 | iso_df = pd.read_hdf(filename, 'iso_df')
258 | iso_df.head()
259 | ```
260 |
261 | ```output
262 | mag_g color_g_i
263 | 0 28.294743 2.195021
264 | 1 28.189718 2.166076
265 | 2 28.051761 2.129312
266 | 3 27.916194 2.093721
267 | 4 27.780024 2.058585
268 | ```
269 |
270 | Here is what the isochrone looks like on the color-magnitude diagram.
271 |
272 | ```python
273 | plot_cmd(candidate_df)
274 | plt.plot(iso_df['color_g_i'], iso_df['mag_g']);
275 | ```
276 |
277 | ```output
278 |
279 | ```
280 |
281 | {alt='Color magnitude diagram of our selected stars with theoretical isochrone overlaid as blue curve.'}
282 |
283 | In the bottom half of the figure, the isochrone passes through the
284 | overdense region where the stars are likely to belong to GD-1.
285 |
286 | Although some stars near the top half of the isochrone likely belong to GD-1,
287 | these represent stars that have evolved off the main sequence. The density of GD-1 stars in this region is therefore
288 | much less and the contamination with other stars much greater. So to get the purest sample of GD-1 stars we will select only stars on the main sequence.
289 |
290 | So we will select the part of the isochrone that lies in the overdense region.
291 |
292 | `g_mask` is a Boolean Series that is `True` where `g` is between 18.0 and 21.5.
293 |
294 | ```python
295 | g_all = iso_df['mag_g']
296 |
297 | g_mask = (g_all > 18.0) & (g_all < 21.5)
298 | g_mask.sum()
299 | ```
300 |
301 | ```output
302 | 117
303 | ```
304 |
305 | We can use it to select the corresponding rows in `iso_df`:
306 |
307 | ```python
308 | iso_masked = iso_df[g_mask]
309 | iso_masked.head()
310 | ```
311 |
312 | ```output
313 | mag_g color_g_i
314 | 94 21.411746 0.692171
315 | 95 21.322466 0.670238
316 | 96 21.233380 0.648449
317 | 97 21.144427 0.626924
318 | 98 21.054549 0.605461
319 | ```
320 |
321 | Now, to select the stars in the overdense region, we have to define a
322 | polygon that includes stars near the isochrone.
323 |
324 | ```python
325 | g = iso_masked['mag_g']
326 | left_color = iso_masked['color_g_i'] - 0.06
327 | right_color = iso_masked['color_g_i'] + 0.12
328 | ```
329 |
330 | Here is our plot with these boundaries:
331 |
332 | ```python
333 | plot_cmd(candidate_df)
334 |
335 | plt.plot(left_color, g, label='left color')
336 | plt.plot(right_color, g, label='right color')
337 |
338 | plt.legend();
339 | ```
340 |
341 | ```output
342 |
343 | ```
344 |
345 | {alt='Color magnitude diagram of our selected stars showing left boundary as blue curve and right boundary as orange curve.'}
346 |
347 | ## Which points are in the polygon?
348 |
349 | Matplotlib provides a `Polygon` object that we can use to check which
350 | points fall in the polygon we just constructed.
351 |
352 | To make a `Polygon`, we need to assemble `g`, `left_color`, and
353 | `right_color` into a loop, so the points in `left_color` are connected
354 | to the points of `right_color` in reverse order.
355 |
356 | We will use a "slice index" to reverse the elements of `right_color`.
357 | As explained in the [NumPy
358 | documentation](https://numpy.org/doc/stable/reference/arrays.indexing.html),
359 | a slice index has three parts separated by colons:
360 |
361 | - `start`: The index of the element where the slice starts.
362 |
363 | - `stop`: The index of the element where the slice ends.
364 |
365 | - `step`: The step size between elements.
366 |
367 | ```
368 | reverse_right_color = right_color[::-1]
369 | ```
370 | {:.language-python}
371 |
372 | In this example, `start` and `stop` are omitted, which means all
373 | elements are selected.
374 |
375 | And `step` is `-1`, which means the elements are in reverse order.
376 |
377 | To combine the `left_color` and `right_color` arrays we will use the NumPy `append` function
378 | which takes two arrays as input, and outputs them combined into a single array.
379 |
380 | ```python
381 | import numpy as np
382 | color_loop = np.append(left_color, reverse_right_color)
383 | color_loop.shape
384 | ```
385 |
386 | ```output
387 | (234,)
388 | ```
389 |
390 | We can repeat combine these two lines of code into a single line to create a corresponding loop with the elements of `g` in forward and reverse order.
391 |
392 | ```python
393 | mag_loop = np.append(g, g[::-1])
394 | mag_loop.shape
395 | ```
396 |
397 | ```output
398 | (234,)
399 | ```
400 |
401 | Here is the loop on our plot:
402 |
403 | ```python
404 | plot_cmd(candidate_df)
405 | plt.plot(color_loop, mag_loop);
406 | ```
407 |
408 | ```output
409 |
410 | ```
411 |
412 | {alt='Color magnitude diagram of our selected stars showing polygon defined by boundaries as blue curve.'}
413 |
414 | To make a `Polygon`, it will be useful to put `color_loop` and
415 | `mag_loop` into a `DataFrame`. This is convenient for two reasons - first, `Polygon`
416 | is expecting an Nx2 array and the `DataFrame` provides an easy way for us to pass that
417 | in that is also descriptive for us. Secondly, for reproducibility of our work, we may want
418 | to save the region we use to select stars, and the `DataFrame`, as we have already seen, allows us to save into a variety of formats.
419 |
420 | ```python
421 | loop_df = pd.DataFrame()
422 | loop_df['color_loop'] = color_loop
423 | loop_df['mag_loop'] = mag_loop
424 | loop_df.head()
425 | ```
426 |
427 | ```output
428 | color_loop mag_loop
429 | 0 0.632171 21.411746
430 | 1 0.610238 21.322466
431 | 2 0.588449 21.233380
432 | 3 0.566924 21.144427
433 | 4 0.545461 21.054549
434 | ```
435 |
436 | Now we can pass `loop_df` to `Polygon`:
437 |
438 | ```python
439 | from matplotlib.patches import Polygon
440 |
441 | polygon = Polygon(loop_df)
442 | polygon
443 | ```
444 |
445 | ```output
446 |
447 | ```
448 |
449 | The result is a `Polygon` object which has a `contains_points` method.
450 | This allows us to pass `polygon.contains_points` a list of points and
451 | for each point it will tell us if the point is contained within the polygon.
452 | A point is a tuple with two elements, x and y.
453 |
454 | ::::::::::::::::::::::::::::::::::::::: challenge
455 |
456 | ## Exercise (5 minutes)
457 |
458 | When we encounter a new object, it is good to create a toy example to test
459 | that it does what we think it does. Define a list of two points (represented as two tuples),
460 | one that should be inside the polygon and one that should be outside the polygon. Call
461 | `contains_points` on the polygon we just created, passing it the list of points you
462 | defined, to verify that the results are as expected.
463 |
464 | ::::::::::::::: solution
465 |
466 | ## Solution
467 |
468 | ```python
469 | test_points = [(0.4, 20),
470 | (0.4, 16)]
471 | ```
472 |
473 | ```python
474 | test_inside_mask = polygon.contains_points(test_points)
475 | test_inside_mask
476 | ```
477 |
478 | ```output
479 | array([ True, False])
480 | ```
481 |
482 | The result is an array of Boolean values, and is as expected.
483 |
484 |
485 |
486 | :::::::::::::::::::::::::
487 |
488 | ::::::::::::::::::::::::::::::::::::::::::::::::::
489 |
490 | We are almost ready to select stars whose photometry data falls in
491 | this polygon. But first we need to do some data cleaning.
492 |
493 | ## Save the polygon
494 |
495 | [Reproducibile
496 | research](https://en.wikipedia.org/wiki/Reproducibility#Reproducible_research)
497 | is "the idea that ... the full computational environment used to
498 | produce the results in the paper such as the code, data, etc. can be
499 | used to reproduce the results and create new work based on the
500 | research."
501 |
502 | This lesson is an example of reproducible research because
503 | it contains all of the code needed to reproduce the results, including
504 | the database queries that download the data and the analysis.
505 |
506 | In this episode, we used an isochrone to derive a polygon, which we used
507 | to select stars based on photometry.
508 | So it is important to record the polygon as part of the data analysis pipeline.
509 |
510 | Here is how we can save it in an HDF5 file.
511 |
512 | ```python
513 | filename = 'gd1_data.hdf'
514 | loop_df.to_hdf(filename, 'loop_df')
515 | ```
516 |
517 | ## Selecting based on photometry
518 |
519 | Now we will check how many of the candidate stars are inside the polygon we chose.
520 | `contains_points` expects a list of (x,y) pairs. As with creating the `Polygon`, `DataFrames` are
521 | a convenient way to pass the colors and magnitudes for all of our stars in `candidates_df` to our `Polygon` to see
522 | which candidates are inside the polygon. We will start by putting color and magnitude data from `candidate_df` into a new `DataFrame`.
523 |
524 | ```python
525 | cmd_df = pd.DataFrame()
526 |
527 | cmd_df['color'] = candidate_df['g_mean_psf_mag'] - candidate_df['i_mean_psf_mag']
528 | cmd_df['mag'] = candidate_df['g_mean_psf_mag']
529 |
530 | cmd_df.head()
531 | ```
532 |
533 | ```output
534 | color mag
535 | 0 0.3804 17.8978
536 | 1 1.6092 19.2873
537 | 2 0.4457 16.9238
538 | 3 1.5902 19.9242
539 | 4 1.4853 16.1516
540 | ```
541 |
542 | Which we can pass to `contains_points`:
543 |
544 | ```python
545 | inside_mask = polygon.contains_points(cmd_df)
546 | inside_mask
547 | ```
548 |
549 | ```output
550 | array([False, False, False, ..., False, False, False])
551 | ```
552 |
553 | The result is a Boolean array.
554 |
555 | ::::::::::::::::::::::::::::::::::::::: challenge
556 |
557 | ## Exercise (5 minutes)
558 |
559 | Boolean values are stored as 0s and 1s. `FALSE` = 0 and `TRUE` = 1. Use this information
560 | to determine the number of stars that fall inside the polygon.
561 |
562 | ::::::::::::::: solution
563 |
564 | ## Solution
565 |
566 | ```python
567 | inside_mask.sum()
568 | ```
569 |
570 | ```output
571 | 486
572 | ```
573 |
574 | :::::::::::::::::::::::::
575 |
576 | ::::::::::::::::::::::::::::::::::::::::::::::::::
577 |
578 | Now we can use `inside_mask` as a mask to select stars that fall inside the polygon.
579 |
580 | ```python
581 | winner_df = candidate_df[inside_mask]
582 | ```
583 |
584 | We will make a color-magnitude plot one more time, highlighting the
585 | selected stars with green markers.
586 |
587 | ```python
588 | plot_cmd(candidate_df)
589 | plt.plot(iso_df['color_g_i'], iso_df['mag_g'])
590 | plt.plot(color_loop, mag_loop)
591 |
592 | x = winner_df['g_mean_psf_mag'] - winner_df['i_mean_psf_mag']
593 | y = winner_df['g_mean_psf_mag']
594 | plt.plot(x, y, 'go', markersize=0.5, alpha=0.5);
595 | ```
596 |
597 | ```output
598 |
599 | ```
600 |
601 | {alt='Color magnitude diagram showing our selected stars in green, inside our polygon.'}
602 |
603 | The selected stars are, in fact, inside the polygon,
604 | which means they have photometry data consistent with GD-1.
605 |
606 | Finally, we can plot the coordinates of the selected stars:
607 |
608 | ```python
609 | fig = plt.figure(figsize=(10,2.5))
610 |
611 | x = winner_df['phi1']
612 | y = winner_df['phi2']
613 | plt.plot(x, y, 'ko', markersize=0.7, alpha=0.9)
614 |
615 | plt.xlabel('$\phi_1$ [deg]')
616 | plt.ylabel('$\phi_2$ [deg]')
617 | plt.title('Proper motion + photometry selection', fontsize='medium')
618 |
619 | plt.axis('equal');
620 | ```
621 |
622 | ```output
623 |
624 | ```
625 |
626 | {alt='phi 1 and phi 2 of selected stars in the GD-1 frame after selecting for both proper motion and photometry.'}
627 |
628 | This example includes the new Matplotlib command `figure`, which creates the larger canvas that the subplots are placed on. In previous examples, we didn't have
629 | to use this function; the figure was created automatically. But when
630 | we call it explicitly, we can provide arguments like `figsize`, which
631 | sets the size of the figure. It also returns a figure object which we will
632 | use to further customize our plotting in the next episode.
633 |
634 | In the example above we also used TeX markup in our axis labels so that they render as the
635 | Greek letter `$\phi$` with subscripts for `1` and `2`.
636 | Matplotlib also allows us to write basic TeX markup by wrapping the text we want
637 | rendered as TeX with `$` and then using TeX commands inside. This basic rendering
638 | is performed with [mathtext](https://matplotlib.org/stable/tutorials/text/mathtext.html);
639 | more advanced rendering with LaTex can be done with the `usetex` option in `rcParams`
640 | which we will discuss in Episode 7.
641 |
642 | In the next episode we are going to make this plot several more times, so it makes sense to
643 | make a function. As we have done with previous functions we can copy and paste what we just wrote,
644 | replacing the specific variable `winner_df` with the more generic `df`.
645 |
646 | ```python
647 | def plot_cmd_selection(df):
648 | x = df['phi1']
649 | y = df['phi2']
650 |
651 | plt.plot(x, y, 'ko', markersize=0.7, alpha=0.9)
652 |
653 | plt.xlabel('$\phi_1$ [deg]')
654 | plt.ylabel('$\phi_2$ [deg]')
655 | plt.title('Proper motion + photometry selection', fontsize='medium')
656 |
657 | plt.axis('equal')
658 | ```
659 |
660 | And here is how we use the function.
661 |
662 | ```python
663 | fig = plt.figure(figsize=(10,2.5))
664 | plot_cmd_selection(winner_df)
665 | ```
666 |
667 | ```output
668 |
669 | ```
670 |
671 | {alt='phi 1 and phi 2 of selected stars in the GD-1 frame after selecting for both proper motion and photometry.'}
672 |
673 | ## Write the data
674 |
675 | Finally, we will write the selected stars to a file.
676 |
677 | ```python
678 | filename = 'gd1_data.hdf'
679 | winner_df.to_hdf(filename, 'winner_df')
680 | ```
681 |
682 | ```python
683 | MB = 1024 * 1024
684 | getsize(filename) / MB
685 | ```
686 |
687 | ```output
688 | 15.486198425292969
689 | ```
690 |
691 | ## Summary
692 |
693 | In this episode, we used photometry data from Pan-STARRS to draw a
694 | color-magnitude diagram.
695 | We used an isochrone to define a polygon and select stars we think are
696 | likely to be in GD-1. Plotting the results, we have a clearer picture
697 | of GD-1, similar to Figure 1 in the original paper.
698 |
699 | :::::::::::::::::::::::::::::::::::::::: keypoints
700 |
701 | - Matplotlib provides operations for working with points, polygons, and other geometric entities, so it is not just for making figures.
702 | - Use Matplotlib options to control the size and aspect ratio of figures to make them easier to interpret.
703 | - Record every element of the data analysis pipeline that would be needed to replicate the results.
704 |
705 | ::::::::::::::::::::::::::::::::::::::::::::::::::
706 |
707 |
708 |
--------------------------------------------------------------------------------
/episodes/build.sh:
--------------------------------------------------------------------------------
1 | # copy notebooks from jb
2 |
3 | cp ~/AstronomicalData/01_query.ipynb 01-query.ipynb
4 | cp ~/AstronomicalData/02_coords.ipynb 02-coords.ipynb
5 |
6 | # convert tags to comments
7 | python convert.py
8 |
9 | # convert ipynb to md
10 | jupyter nbconvert --to markdown 01-query.ipynb
11 | jupyter nbconvert --to markdown 02-coords.ipynb
12 |
13 |
14 | # push it to GutHub
15 | #git add 01-query.md
16 | #git add 02-coords.md
17 | #git commit -m "Automated push from AstronomicalData"
18 | #git push
19 |
--------------------------------------------------------------------------------
/episodes/convert.py:
--------------------------------------------------------------------------------
1 | import nbformat as nbf
2 | from glob import glob
3 | #from pypandoc import convert_text
4 | import re
5 | from textwrap import wrap
6 |
7 | # Collect a list of all notebooks in the content folder
8 | notebooks = glob("*.ipynb")
9 |
10 | # Text to look for in adding tags
11 | tag_search_dict = {
12 | "remove-cell": "#hide\n",
13 | "hide-cell": "#hide\n",
14 | "remove-input": "#hide_input\n",
15 | "hide-input": "#hide_input\n",
16 | "remove-output": "#hide_output\n",
17 | "hide-output": "#hide_output\n",
18 | }
19 |
20 | WRAP_OPTIONS = dict(break_long_words=False,
21 | break_on_hyphens=False)
22 |
23 | def wrap_source(source):
24 | res = []
25 | for line in source.split('\n'):
26 | if len(line) > 80:
27 | line = '\n'.join(wrap(line, **WRAP_OPTIONS))
28 | res.append(line)
29 | return '\n'.join(res)
30 |
31 | def remove_first_line(text):
32 | _, _, rest = text.partition('\n')
33 | return rest
34 |
35 | def indent_text(text, prefix):
36 | res = []
37 | for line in text.split('\n'):
38 | res.append(prefix + line + '\n')
39 | return ''.join(res)
40 |
41 | def format_exercise(cell):
42 | res = []
43 | res.append('> ## Exercise\n')
44 | text = remove_first_line(cell['source'])
45 | res.append(indent_text(text, '> '))
46 | return ''.join(res)
47 |
48 |
49 | def process_markdown(cell):
50 | if cell['source'].startswith('### Exercise'):
51 | cell['source'] = format_exercise(cell)
52 | else:
53 | cell['source'] = re.sub('\n>', '\n> :', cell['source'])
54 | cell['source'] = wrap_source(cell['source'])
55 |
56 |
57 | def process_code(cell):
58 | """Translate code blocks
59 | """
60 | text = cell['source']
61 |
62 | # If it's a solution, remove the first line
63 | is_solution = text.startswith('# Solution')
64 | if is_solution:
65 | text = remove_first_line(text)
66 |
67 | # Assemble the exercise formatting
68 | cell['cell_type'] = 'raw'
69 | res = []
70 | res.append('\n~~~')
71 | res.append(text)
72 | res.append('~~~')
73 | res.append('{: .language-python}')
74 |
75 | if cell['outputs']:
76 | res.append('\n~~~')
77 | for output in cell['outputs']:
78 | try:
79 | res.append(output['text'])
80 | except KeyError:
81 | """Ignoring non-text output"""
82 | pass
83 | res.append('~~~')
84 | res.append('{: .output}\n')
85 |
86 | cell['source'] = '\n'.join(res)
87 |
88 | # If it's a solution, indent it and add the suffix
89 | if is_solution:
90 | cell['source'] = indent_text(cell['source'], '> > ')
91 | cell['source'] += '> {: .solution}\n'
92 | cell['source'] += '{: .challenge}\n'
93 |
94 | def process_cell(cell):
95 | if cell['cell_type'] == 'raw':
96 | return
97 |
98 | cell_tags = cell.get('metadata', {}).get('tags', [])
99 | if 'remove-cell' in cell_tags:
100 | cell['cell_type'] = 'raw'
101 | cell['source'] = ''
102 | cell['outputs'] = []
103 | return
104 |
105 | if cell['cell_type'] == 'markdown':
106 | process_markdown(cell)
107 |
108 | if cell['cell_type'] == 'code':
109 | process_code(cell)
110 |
111 | def process_notebook(path):
112 | """
113 | """
114 | ntbk = nbf.read(path, nbf.NO_CONVERT)
115 |
116 | for cell in ntbk.cells:
117 | process_cell(cell)
118 |
119 | nbf.write(ntbk, path)
120 |
121 | # Search through each notebook and look for the text, add a tag if necessary
122 | for path in notebooks:
123 | print('converting', path)
124 | process_notebook(path)
125 |
--------------------------------------------------------------------------------
/episodes/data/gd1_candidates.hdf5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_candidates.hdf5
--------------------------------------------------------------------------------
/episodes/data/gd1_dataframe.hdf5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_dataframe.hdf5
--------------------------------------------------------------------------------
/episodes/data/gd1_df5.hdf5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_df5.hdf5
--------------------------------------------------------------------------------
/episodes/data/gd1_isochrone.hdf5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_isochrone.hdf5
--------------------------------------------------------------------------------
/episodes/data/gd1_merged.hdf5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_merged.hdf5
--------------------------------------------------------------------------------
/episodes/data/gd1_photo.fits:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_photo.fits
--------------------------------------------------------------------------------
/episodes/data/gd1_results.fits:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/data/gd1_results.fits
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_102_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_102_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_103_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_103_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_104_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_104_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_112_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_112_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_28_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_28_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_29_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_29_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_30_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_30_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_43_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_43_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_45_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_45_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_46_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_46_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_47_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_47_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_68_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_68_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_70_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_70_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_71_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_71_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_78_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_78_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_80_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_80_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_82_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_82_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_84_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_84_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_85_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_85_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_86_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_86_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_87_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_87_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_90_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_90_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_93_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_93_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_94_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_94_0.png
--------------------------------------------------------------------------------
/episodes/fig/03-transform_files/03-transform_97_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/03-transform_files/03-transform_97_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_100_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_100_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_102_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_102_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_103_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_103_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_104_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_104_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_110_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_110_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_112_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_112_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_29_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_29_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_30_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_30_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_45_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_45_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_46_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_46_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_47_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_47_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_67_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_67_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_68_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_68_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_69_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_69_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_70_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_70_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_71_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_71_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_78_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_78_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_80_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_80_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_82_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_82_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_84_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_84_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_85_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_85_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_86_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_86_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_87_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_87_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_90_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_90_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_92_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_92_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_93_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_93_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_94_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_94_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_97_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_97_0.png
--------------------------------------------------------------------------------
/episodes/fig/04-motion_files/04-motion_plot_pm_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/04-motion_files/04-motion_plot_pm_selection.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_12_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_12_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_13_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_13_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_14_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_14_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_15_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_15_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_16_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_16_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_17_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_17_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_28_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_28_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_29_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_29_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_30_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_30_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_31_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_31_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_56_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_56_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_57_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_57_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_58_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_58_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_62_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_62_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_63_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_63_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_64_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_64_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_65_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_65_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_66_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_66_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_67_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_67_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_70_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_70_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_71_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_71_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_72_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_72_0.png
--------------------------------------------------------------------------------
/episodes/fig/05-select_files/05-select_73_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/05-select_files/05-select_73_0.png
--------------------------------------------------------------------------------
/episodes/fig/06-join_files/06-join_10_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/06-join_files/06-join_10_0.png
--------------------------------------------------------------------------------
/episodes/fig/06-join_files/06-join_70_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/06-join_files/06-join_70_0.png
--------------------------------------------------------------------------------
/episodes/fig/06-join_files/06-join_76_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/06-join_files/06-join_76_0.png
--------------------------------------------------------------------------------
/episodes/fig/06-join_files/06-join_78_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/06-join_files/06-join_78_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-cmd_lims.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-cmd_lims.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-cmd_no_lims.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-cmd_no_lims.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_106_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_106_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_108_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_108_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_114_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_114_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_116_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_116_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_11_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_11_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_12_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_12_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_13_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_13_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_14_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_14_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_41_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_41_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_42_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_42_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_43_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_43_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_44_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_44_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_46_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_46_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_51_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_51_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_52_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_52_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_53_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_53_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_54_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_54_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_61_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_61_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_62_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_62_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_63_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_63_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_64_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_64_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_66_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_66_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_69_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_69_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_70_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_70_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_71_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_71_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_72_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_72_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_90_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_90_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_91_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_91_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_92_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_92_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_93_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_93_0.png
--------------------------------------------------------------------------------
/episodes/fig/07-photo_files/07-photo_94_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/07-photo_files/07-photo_94_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_12_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_12_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_13_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_13_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_14_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_14_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_51_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_51_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_52_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_52_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_53_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_53_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_54_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_54_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_58_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_58_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_59_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_59_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_61_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_61_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_62_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_62_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_63_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_63_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_64_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_64_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_67_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_67_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_68_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_68_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_69_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_69_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_70_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_70_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_71_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_71_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_72_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_72_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_73_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_73_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_75_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_75_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_76_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_76_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_78_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_78_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_79_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_79_0.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_adjusted_size_fig1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_adjusted_size_fig1.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-plot_equal_size_fig1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-plot_equal_size_fig1.png
--------------------------------------------------------------------------------
/episodes/fig/08-plot_files/08-poly_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/08-plot_files/08-poly_example.png
--------------------------------------------------------------------------------
/episodes/fig/Gaia-HR-diagram.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/Gaia-HR-diagram.jpeg
--------------------------------------------------------------------------------
/episodes/fig/gd1-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/gd1-1.png
--------------------------------------------------------------------------------
/episodes/fig/gd1-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/gd1-2.png
--------------------------------------------------------------------------------
/episodes/fig/gd1-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/gd1-3.png
--------------------------------------------------------------------------------
/episodes/fig/gd1-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/gd1-4.png
--------------------------------------------------------------------------------
/episodes/fig/gd1-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/gd1-5.png
--------------------------------------------------------------------------------
/episodes/fig/join.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/join.odg
--------------------------------------------------------------------------------
/episodes/fig/join.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/episodes/fig/join.png
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: index.html
3 | site: sandpaper::sandpaper_site
4 | ---
5 |
6 | The Foundations of Astronomical Data Science curriculum covers a range of core concepts necessary to efficiently study the ever-growing datasets developed in modern astronomy. In particular, this curriculum teaches learners to perform database operations (SQL queries, joins, filtering) and to create publication-quality data visualisations. Learners will use software packages common to the general and astronomy-specific data science communities ([Pandas](https://pandas.pydata.org), [Astropy](https://www.astropy.org), [Astroquery](https://astroquery.readthedocs.io/en/latest/) combined with two astronomical datasets: the large, all-sky, multi-dimensional dataset from the [Gaia satellite](https://sci.esa.int/web/gaia), which measures the positions, motions, and distances of approximately a billion stars in our Milky Way galaxy with unprecedented accuracy and precision; and the [Pan-STARRS photometric survey](https://panstarrs.stsci.edu/), which precisely measures light output and distribution from many stars. Together, the software and datasets are used to reproduce part of the analysis from the article ["Off the beaten path: Gaia reveals GD-1 stars outside of the main stream"](https://arxiv.org/abs/1805.00425) by Drs. Adrian M. Price-Whelan and Ana Bonaca. This lesson shows how to identify and visualize the GD-1 stellar stream, which is a globular cluster that has been tidally stretched by the Milky Way.
7 |
8 | GD-1 is a stellar stream around the Milky Way. This means it is a collection of stars that we believe was once part of a bound clump, but the gravitational influence of the Milky Way has torn it apart and spread it over an arc that traces out its orbit on the sky. This is interesting, because if the original bound clump was a dwarf galaxy, understanding its orbit with sufficient precision allows us to measure the mass of the Milky Way, which is very important for understanding the future and past of the Milky Way as a whole. But that is much easier to do if we have a coordinate system aligned with the stream because that makes fitting the location of the stars much easier mathematically - it becomes more linear instead of some complicated curve. Additionally, this stream is especially interesting because it has "gaps", which have a natural interpretation as being caused by the influence of small clumps of dark matter passing near the stream. Knowing the typical rate of these gaps tells you about the typical size and density of these clumps, which turns out to be one of the best probes we have of the fine structure of dark matter.
9 |
10 | This lesson can be taught in approximately 10 hours and covers the following topics:
11 |
12 | - Incremental creation of complex ADQL and SQL queries.
13 | - Using Astroquery to query a remote server in Python.
14 | - Transforming coordinates between common coordinate systems using Astropy units and coordinates.
15 | - Working with common astronomical file formats, including FITS, HDF5, and CSV.
16 | - Managing your data with Pandas DataFrames and Astropy Tables.
17 | - Writing functions to make your work less error-prone and more reproducible.
18 | - Creating a reproducible workflow that brings the computation to the data.
19 | - Customising all elements of a plot and creating complex, multi-panel, publication-quality graphics.
20 |
21 | Interested in teaching these materials? We have an [onboarding video](https://www.youtube.com/watch?v=gfaNFaKIOrY) and accompanying
22 | [slides](https://docs.google.com/presentation/d/1YosDXx1gBGpBxf6fCEaazFQwZ2dYTWgtYSdPEeD09yo/edit#slide=id.p) available to prepare Instructors to
23 | teach this lesson. After watching this video, please contact [team@carpentries.org](mailto:team@carpentries.org) so that we can record your status as an onboarded Instructor. Instructors who have completed onboarding will be given priority status for teaching at Centrally-Organised
24 | Data Carpentry Foundations of Astronomical Data Science workshops.
25 |
26 | :::::::::::::::::::::::::::::::::::::::::: prereq
27 |
28 | ## Prerequisites
29 |
30 | This lesson assumes you have a working knowledge of Python and some previous exposure to the Bash shell.
31 | These requirements can be fulfilled by:
32 | a) completing a Software Carpentry Python workshop **or**
33 | b) completing a Data Carpentry Ecology workshop (with Python) **and** a Data Carpentry Genomics workshop **or**
34 | c) independent exposure to both Python and the Bash shell.
35 |
36 | If you're unsure whether you have enough experience to participate in this workshop, please read over
37 | [this detailed list](instructors/prereqs.md), which gives all of the functions, operators, and other concepts you will need
38 | to be familiar with.
39 |
40 | In addition, this lesson assumes that learners have some familiarity with astronomical concepts, including
41 | reference frames, proper motion, color-magnitude diagrams, globular clusters, and isochrones. Participants should bring their own laptops and plan to participate actively.
42 |
43 |
44 | ::::::::::::::::::::::::::::::::::::::::::::::::::
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/instructors/calculating_MIST_isochrone.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Making the Isochrone DataFrame
3 | ---
4 |
5 | ::::::::::::::::::::::::::::::::::::::::: callout
6 |
7 | ## Calculating Isochrone
8 |
9 | In fact, we can use [MESA Isochrones \& Stellar Tracks](https://waps.cfa.harvard.edu/MIST/) (MIST)
10 | to compute it for us.
11 | Using the [MIST Version 1.2 web interface](https://waps.cfa.harvard.edu/MIST/interp_isos.html),
12 | we computed an isochrone with the following parameters:
13 |
14 | - Rotation initial v/v\_crit = 0.4
15 | - Single age, linear scale = 12e9
16 | - Composition [Fe/H] = -1.35
17 | - Synthetic Photometry, PanStarrs
18 | - Extinction av = 0
19 |
20 |
21 | ::::::::::::::::::::::::::::::::::::::::::::::::::
22 |
23 | The following cell downloads the results:
24 |
25 | ```python
26 | download('https://github.com/AllenDowney/AstronomicalData/raw/main/' +
27 | 'data/MIST_iso_5fd2532653c27.iso.cmd')
28 | ```
29 |
30 | To read this file we will download a Python module [from this
31 | repository](https://github.com/jieunchoi/MIST_codes).
32 |
33 | ```python
34 | download('https://github.com/jieunchoi/MIST_codes/raw/master/scripts/' +
35 | 'read_mist_models.py')
36 | ```
37 |
38 | Now we can read the file:
39 |
40 | ```python
41 | import read_mist_models
42 |
43 | filename = 'MIST_iso_5fd2532653c27.iso.cmd'
44 | iso = read_mist_models.ISOCMD(filename)
45 | ```
46 |
47 | ```output
48 | Reading in: MIST_iso_5fd2532653c27.iso.cmd
49 | ```
50 |
51 | The result is an `ISOCMD` object.
52 |
53 | ```python
54 | type(iso)
55 | ```
56 |
57 | ```output
58 | read_mist_models.ISOCMD
59 | ```
60 |
61 | It contains a list of arrays, one for each isochrone.
62 |
63 | ```python
64 | type(iso.isocmds)
65 | ```
66 |
67 | ```output
68 | list
69 | ```
70 |
71 | We only got one isochrone.
72 |
73 | ```python
74 | len(iso.isocmds)
75 | ```
76 |
77 | ```output
78 | 1
79 | ```
80 |
81 | So we can select it like this:
82 |
83 | ```python
84 | iso_array = iso.isocmds[0]
85 | ```
86 |
87 | It is a NumPy array:
88 |
89 | ```python
90 | type(iso_array)
91 | ```
92 |
93 | ```output
94 | numpy.ndarray
95 | ```
96 |
97 | But it is an unusual NumPy array, because it contains names for the columns.
98 |
99 | ```python
100 | iso_array.dtype
101 | ```
102 |
103 | ```output
104 | dtype([('EEP', '= 0) & (iso_array['phase'] < 3)
122 | phase_mask.sum()
123 | ```
124 |
125 | ```output
126 | 354
127 | ```
128 |
129 | ```python
130 | main_sequence = iso_array[phase_mask]
131 | len(main_sequence)
132 | ```
133 |
134 | ```output
135 | 354
136 | ```
137 |
138 | The other two columns we will use are `PS_g` and `PS_i`, which contain
139 | simulated photometry data for stars with the given age and
140 | metallicity, based on a model of the Pan-STARRS sensors.
141 |
142 | We will use these columns to superimpose the isochrone on the
143 | color-magnitude diagram, but first we have to use a [distance
144 | modulus](https://en.wikipedia.org/wiki/Distance_modulus) to scale the
145 | isochrone based on the estimated distance of GD-1.
146 |
147 | We can use the `Distance` object from Astropy to compute the distance modulus.
148 |
149 | ```python
150 | import astropy.coordinates as coord
151 | import astropy.units as u
152 |
153 | distance = 7.8 * u.kpc
154 | distmod = coord.Distance(distance).distmod.value
155 | distmod
156 | ```
157 |
158 | ```output
159 | 14.4604730134524
160 | ```
161 |
162 | Now we can compute the scaled magnitude and color of the isochrone.
163 |
164 | ```python
165 | mag_g = main_sequence['PS_g'] + distmod
166 | color_g_i = main_sequence['PS_g'] - main_sequence['PS_i']
167 | ```
168 |
169 | Now we can plot it on the color-magnitude diagram like this.
170 |
171 | ```python
172 | plot_cmd(candidate_df)
173 | plt.plot(color_g_i, mag_g);
174 | ```
175 |
176 | ```output
177 |
178 | ```
179 |
180 | {alt='Color magnitude diagram of our selected stars with theoretical isochrone overlaid as blue curve.'}
181 |
182 | The theoretical isochrone passes through the overdense region where we
183 | expect to find stars in GD-1.
184 |
185 | We will save this result so we can reload it later without repeating the
186 | steps in this section.
187 |
188 | So we can save the data in an HDF5 file, we will put it in a Pandas
189 | `DataFrame` first:
190 |
191 | ```python
192 | import pandas as pd
193 |
194 | iso_df = pd.DataFrame()
195 | iso_df['mag_g'] = mag_g
196 | iso_df['color_g_i'] = color_g_i
197 |
198 | iso_df.head()
199 | ```
200 |
201 | ```output
202 | mag_g color_g_i
203 | 0 28.294743 2.195021
204 | 1 28.189718 2.166076
205 | 2 28.051761 2.129312
206 | 3 27.916194 2.093721
207 | 4 27.780024 2.058585
208 | ```
209 |
210 | And then save it.
211 |
212 | ```python
213 | filename = 'gd1_isochrone.hdf5'
214 | iso_df.to_hdf(filename, 'iso_df')
215 | ```
216 |
217 |
218 |
--------------------------------------------------------------------------------
/instructors/link-list.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Link List
3 | ---
4 |
5 | ## Journal articles
6 |
7 | - [Constraining the Milky Way Potential with a Six-Dimensional Phase-Space Map of the GD-1 Stellar Stream](https://ui.adsabs.harvard.edu/abs/2010ApJ...712..260K/abstract)
8 | - [Off the beaten path: Gaia reveals GD-1 stars outside of the main stream](https://arxiv.org/abs/1805.00425)
9 |
10 | ## Jupyter Notebook
11 |
12 | - [How to Use Jupyter Notebook in 2020: A Beginner's Tutorial](https://www.dataquest.io/blog/jupyter-notebook-tutorial/)
13 | - [matplotlib magic](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-matplotlib)
14 | - [Try Jupyter quick start](https://jupyter.org/try)
15 |
16 | ## SQL and ADQL
17 |
18 | - [ADQL CONTAINS](https://www.ivoa.net/documents/ADQL/20180112/PR-ADQL-2.1-20180112.html#tth_sEc4.2.12)
19 | - [ADQL documentation](https://www.ivoa.net/documents/ADQL/20180112/PR-ADQL-2.1-20180112.html)
20 | - [An Astronomy Data Query Language cookbook to accompany Gaia Data Release 1](https://www.gaia.ac.uk/data/gaia-data-release-1/adql-cookbook)
21 | - [Astroquery documentation](https://astroquery.readthedocs.io/en/latest/)
22 | - [Gaia ADQL example queries](https://gea.esac.esa.int/archive-help/adql/examples/index.html)
23 | - [SQL (Wikipedia )](https://en.wikipedia.org/wiki/SQL)
24 | - [SQL JOIN](https://www.geeksforgeeks.org/sql-join-set-1-inner-left-right-and-full-joins/)
25 | - [SQL operators](https://www.w3schools.com/sql/sql_operators.asp)
26 | - [SQL USING](https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqljusing.html)
27 |
28 | ## GAIA
29 |
30 | - [Crossmatch with external catalogues](https://gea.esac.esa.int/archive/documentation/GDR2/Catalogue_consolidation/chap_cu9val_cu9val/ssec_cu9xma/)
31 | - [Crossmatches](https://gea.esac.esa.int/archive/documentation/GDR2/Gaia_archive/chap_datamodel/sec_dm_crossmatches/)
32 | - [Gaia ADQL example queries](https://gea.esac.esa.int/archive-help/adql/examples/index.html)
33 | - [GAIA data release 2 - Homepage](https://www.cosmos.esa.int/web/gaia/dr2)
34 | - [GAIA data release documentation - Main tables](https://gea.esac.esa.int/archive/documentation/GDR2/Gaia_archive/chap_datamodel/sec_dm_main_tables/ssec_dm_gaia_source.html)
35 | - [GAIA Frequently Asked Questions (FAQ)](https://www.cosmos.esa.int/web/gaia/archive-tips)
36 | - [Gaia's Hertzsprung-russell diagram](https://sci.esa.int/web/gaia/-/60198-gaia-hertzsprung-russell-diagram)
37 | - [GAIA TAP+ REST service](https://astroquery.readthedocs.io/en/latest/gaia/gaia.html)
38 | - [Information about GAIA Data Release 2](https://www.cosmos.esa.int/web/gaia/data-release-2)
39 | - [Scanning Law](https://www.cosmos.esa.int/web/gaia/scanning-law)
40 |
41 | ## Python (General)
42 |
43 | - [Strings](https://docs.python.org/3/tutorial/introduction.html#strings)
44 | - [Python String format() Method](https://www.w3schools.com/python/ref_string_format.asp)
45 | - [pandas.DataFrame](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html)
46 | - [NumPy indexing](https://numpy.org/doc/stable/reference/arrays.indexing.html)
47 | - [Context Managers](https://book.pythontips.com/en/latest/context_managers.html)
48 | - [Pandas input output tools (text, CSV, HDF5)](https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html)
49 |
50 | ## Astropy
51 |
52 | - [AstroPy](https://www.astropy.org)
53 | - [AstroPy Tables](https://docs.astropy.org/en/stable/table/)
54 | - [Accessing a Table](https://docs.astropy.org/en/stable/table/access_table.html)
55 | - [Units and Quantities](https://docs.astropy.org/en/stable/units/)
56 | - [Gala](https://gala-astro.readthedocs.io/en/latest/)
57 | - [Source code for gala.coordinates.gd1](https://gala-astro.readthedocs.io/en/latest/_modules/gala/coordinates/gd1.html)
58 | - [gala.coordinates.reflex\_correct](https://gala-astro.readthedocs.io/en/latest/api/gala.coordinates.reflex_correct.html)
59 | - [Matching catalogs](https://docs.astropy.org/en/stable/coordinates/matchsep.html#matching-catalogs)
60 |
61 | ## Matplotlib
62 |
63 | - [Adding subplots](https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.add_subplot)
64 | - [Add text to axes with matplotlib.pyplot.text](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.text.html)
65 | - [Annotating text with matplotlib](https://matplotlib.org/stable/tutorials/text/annotations.html)
66 | - [Change the appearance of ticks, tick labels, and gridlines](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.tick_params.html)
67 | - [Create a subplot at a specific location inside a regular grid](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplot2grid.html)
68 | - [Customizing Matplotlib with style sheets and rcParams](https://matplotlib.org/stable/tutorials/introductory/customizing.html)
69 | - [How to avoid overplotting with python](https://python-graph-gallery.com/134-how-to-avoid-overplotting-with-python/)
70 | - [matplotlib](https://matplotlib.org/)
71 | - [matplotlib magic for Jupyter Notebook](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-matplotlib)
72 | - [matplotlib.pyplot.axvline](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.axvline.html)
73 | - [matplotlib.pyplot.plot](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html)
74 | - [Scatter plots with matplotlib.pyplot.scatter](https://matplotlib.org/3.3.0/api/_as_gen/matplotlib.pyplot.scatter.html)
75 | - [Simple scatter plots](https://jakevdp.github.io/PythonDataScienceHandbook/04.02-simple-scatter-plots.html)
76 | - [Sample matplotlibrc file](https://matplotlib.org/stable/tutorials/introductory/customizing.html#matplotlibrc-sample)
77 | - [Style sheets reference](https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html)
78 | - [Tight Layout guide](https://matplotlib.org/stable/tutorials/intermediate/tight_layout_guide.html)
79 | - [Vertical lines with matplotlib.pyplot.vlines](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.vlines.html)
80 | - [Writing mathematical expressions](https://matplotlib.org/stable/tutorials/text/mathtext.html)
81 |
82 | ## File Formats
83 |
84 | - [Comma-separated values (CSV) (Wikipedia)](https://en.wikipedia.org/wiki/Comma-separated_values)
85 | - [Flexible Image Transport System (FITS) (Wikipedia)](https://en.wikipedia.org/wiki/FITS)
86 | - [Hierarchical Data Format (HDF) (Wikipedia)](https://en.wikipedia.org/wiki/Hierarchical_Data_Format)
87 | - [Multi-Extension FITS File Format](https://hst-docs.stsci.edu/hstdhb/3-hst-file-formats/3-2-fits-file-format)
88 | - [Pandas input output tools (text, CSV, HDF5)](https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html)
89 |
90 | ## Astronomy background information
91 |
92 | - [Betelgeuse](https://simbad.u-strasbg.fr/simbad/sim-basic?Ident=Betelgeuse)
93 | - [Celestial coordinate system (Wikipeda)](https://en.wikipedia.org/wiki/Celestial_coordinate_system)
94 | - [Color-Magnitude and Color-Color plots Overview](https://coolwiki.ipac.caltech.edu/index.php/Color-Magnitude_and_Color-Color_plots_Overview)
95 | - [International Celestial Reference System (ICRS)](https://www.iers.org/IERS/EN/Science/ICRS/ICRS.html)
96 | - [International Celestial Reference System (ICRS) (Wikipedia)](https://en.wikipedia.org/wiki/International_Celestial_Reference_System)
97 | - [International Virtual Observatory Alliance (IVOA) Table Access Protocol (TAP)](https://www.ivoa.net/documents/TAP/)
98 | - [Isochrone interpolation](https://waps.cfa.harvard.edu/MIST/interp_isos.html)
99 | - [MESA Isochrones \& Stellar Tracks](https://waps.cfa.harvard.edu/MIST/)
100 | - [Ordinary stars and HR diagrams](https://spiff.rit.edu/classes/ladder/lectures/ordinary_stars/ordinary.html)
101 | - [Photometric system (Wikipedia)](https://en.wikipedia.org/wiki/Photometric_system)
102 | - [Stellar isochrone (Wikipedia)](https://en.wikipedia.org/wiki/Stellar_isochrone)
103 |
104 | ## Other background information
105 |
106 | - [Figure Accessibility Guidelines](https://journals.aas.org/graphics-guide/)
107 | - [Floating point arithmetic (Wikipedia)](https://en.wikipedia.org/wiki/Floating-point_arithmetic)
108 | - [Indexing](https://chartio.com/learn/databases/how-does-indexing-work/)
109 | - [Markdown guide](https://www.markdownguide.org/)
110 | - [Minutes and seconds of Arc (Wikipedia)](https://en.wikipedia.org/wiki/Minute_and_second_of_arc)
111 | - [Reproducible research (Wikipedia)](https://en.wikipedia.org/wiki/Reproducibility#Reproducible_research)
112 | - [Transpose (Wikipedia)](https://en.wikipedia.org/wiki/Transpose)
113 |
114 |
115 |
--------------------------------------------------------------------------------
/instructors/prereqs.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Prerequisites
3 | ---
4 |
5 | This lesson assumes you have a working knowledge of Python and some previous exposure to the Bash shell.
6 |
7 | These requirements can be fulfilled by:
8 |
9 | 1. completing a Software Carpentry Python workshop **or**
10 | 2. completing a Data Carpentry Ecology workshop (with Python) **and** a Data Carpentry Genomics workshop **or**
11 | 3. coursework in or independent learning of both Python and the Bash shell.
12 |
13 | ### Bash shell skills
14 |
15 | The skill set listed below is covered in any Software Carpentry workshop, as well
16 | as in Data Carpentry's Genomics workshop. These skills can also be learned
17 | through coursework or independent learning.
18 |
19 | Be able to:
20 |
21 | - Identify and navigate to your home directory.
22 | - Identify your current working directory.
23 | - Navigating directories using `pwd`, `ls`, `cd `, and `cd ..`
24 |
25 | ### Python skills
26 |
27 | This skill set listed below is covered in both Software Carpentry's Python workshop and
28 | in Data Carpentry's Ecology workshop with Python. These skills can also be learned
29 | through coursework or independent learning.
30 |
31 | Be able to:
32 |
33 | - Perform basic arithmetic operations (e.g. addition, subtraction) on variables.
34 | - Convert strings to ints or floats where appropriate.
35 | - Create a `list` and alter lists by appending, inserting, or removing values.
36 | - Use indexing and slicing to access elements of strings, lists, and Numpy arrays.
37 | - Use good coding practices to comment your code and choose appropriate variable names.
38 | - Write a `for` loop that increments a variable.
39 | - Write conditional statements using `if`, `elif`, and `else`.
40 | - Use comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`) in conditional statements.
41 | - Open, read from, write to, and close input and output files.
42 | - Use `print()` and `len()` to inspect variables.
43 |
44 | The following skills are useful, but not required:
45 |
46 | - Apply a function to an entire Numpy array or to a single array axis.
47 | - Write a user-defined function.
48 |
49 | If you are signed up, or considering signing up for a workshop, and aren't sure whether you meet these reqirements, please
50 | get in touch with the workshop instructors or host.
51 |
52 |
53 |
--------------------------------------------------------------------------------
/learners/discuss.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Discussion
3 | ---
4 |
5 | FIXME
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/learners/reference.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Reference'
3 | ---
4 |
5 | ## Glossary
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/learners/setup.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Setup
3 | ---
4 |
5 | ## Overview
6 |
7 | This workshop is designed to be run on your local machine. First, you will need to download the data we
8 | use in the workshop. Then, you need to set up your machine to use the required software. Lastly, you will
9 | download and run a Jupyter Notebook that contains test code to check that your installation was
10 | successful.
11 |
12 | **Optional** - You may also be interested in reading the journal article that we will explore during the workshop -
13 | [Off the Beaten Path: *Gaia* Reveals GD-1 Stars outside of the Main Stream](https://iopscience.iop.org/article/10.3847/2041-8213/aad7b5) by
14 | Adrian M. Price-Whelan and Ana Bonaca.
15 |
16 | ## Data
17 |
18 | To start your installation process download this [zip file](https://figshare.com/ndownloader/files/35777540). Move the downloaded file to your Desktop.
19 | If your file does not automatically unzip into a directory called `student_download`, you can unzip it with the following steps:
20 |
21 | - Mac: navigate in a finder window to your Desktop and double click `student_download.zip`
22 | - Windows: navigate in a file explorer window to your Desktop, right click the `student_download.zip` file, and select `Extract All`
23 | - Linux: open a terminal and navigate to your Desktop. Type `unzip student_download.zip`
24 |
25 | You should now have a directory called `student_download`.
26 | In this directory you will find files that you will use during the workshop as well as files that you will use in the remainder of the set up process.
27 |
28 | ## Software
29 |
30 | You will need to install Python, Jupyter, and some additional libraries.
31 | [Python](https://python.org) is a popular language for
32 | scientific computing, and great for general-purpose programming as
33 | well. For this workshop we use Python version 3.x.
34 | Installing all of its scientific packages individually can be
35 | a bit difficult, so we recommend an all-in-one installer.
36 | We will use Anaconda.
37 |
38 | ### Anaconda
39 |
40 | Download and install [Anaconda](https://www.anaconda.com/download).
41 |
42 | To create a new Conda environment, which includes the additional packages we will be using
43 | in this workshop, you will need the environment file (`environment.yml`) you downloaded in the data section.
44 |
45 | In a Terminal or Jupyter Prompt, make sure you are in the `student_download` directory, where `environment.yml` is stored, and run:
46 |
47 | ```
48 | conda env create -f environment.yml
49 | ```
50 |
51 | Then, to activate the environment you just created, run:
52 |
53 | ```
54 | conda activate AstronomicalData
55 | ```
56 |
57 | ### Jupyter
58 |
59 | We will test our environment setup using a test notebook (`test_setup.ipynb`) that you downloaded in the data section.
60 |
61 | In a Terminal, Jupyter Prompt or Anaconda Prompt, make sure you are in the `student_download` directory.
62 | To start Jupyter, make sure you have activated your new conda environment, then run:
63 |
64 | ```
65 | jupyter notebook
66 | ```
67 |
68 | The notebook should open automatically in your browser. If it does not or you wish to use a different
69 | browser, open this link: [http://localhost:8888](https://localhost:8888).
70 |
71 | Now open the notebook you downloaded, `test_setup.ipynb`, and read through the instructions there.
72 | Make sure to run the cells that contain `import` statements.
73 | If they work and you get no error messages, **you are ready for the workshop**.
74 |
75 | ::::::::::::::::::::::::::::::::::::::::: callout
76 |
77 | ## Why didn't the imports work?
78 |
79 | Occasionally learners will need to take one additional step to make Jupyter run within the environment we have created.
80 | If your imports fail, close Jupyter by closing its terminal, and try running the following from your Anaconda prompt (
81 | Terminal or otherwise):
82 |
83 | ```
84 | python -m ipykernel install --user --name=AstronomicalData
85 | ```
86 |
87 | Then start Jupyter up again:
88 |
89 | ```
90 | jupyter notebook
91 | ```
92 |
93 | This time, when you open your notebook, navigate to the Kernel menu --> Change Kernel --> select **AstronomicalData** .
94 | This will ensure that the relevant packages are all available.
95 | You can seek installation help if this looks confusing!
96 |
97 |
98 | ::::::::::::::::::::::::::::::::::::::::::::::::::
99 |
100 | Please contact your instructors if you experience any problems with these installation instructions. If
101 | you are working through these materials independently, let us know about any problems you encounter by
102 | [filing an issue on the lesson's GitHub repository](https://github.com/datacarpentry/astronomy-python/issues)
103 | or emailing [team@carpentries.org](mailto:team@carpentries.org).
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/profiles/learner-profiles.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: FIXME
3 | ---
4 |
5 | This is a placeholder file. Please add content here.
6 |
--------------------------------------------------------------------------------
/site/README.md:
--------------------------------------------------------------------------------
1 | This directory contains rendered lesson materials. Please do not edit files
2 | here.
3 |
--------------------------------------------------------------------------------
/student_download/az-paper-twocol.mplstyle:
--------------------------------------------------------------------------------
1 | axes.prop_cycle : cycler('color', ['000000','CC6677','4477AA','DDCC77','117733','88CCEE','882255','44AA99','999933','AA4499', '661100', 'AA4466','4477AA'])
2 |
3 |
4 | figure.figsize : 6.0, 3
5 | figure.subplot.bottom: 0.15
6 | figure.subplot.left : 0.1
7 | figure.subplot.right : 0.95
8 | figure.subplot.top : 0.95
9 |
10 | lines.markersize : 5
11 | lines.markeredgewidth : 0.5
12 | lines.linewidth : 1.5
13 |
14 | axes.linewidth : 1.0
15 | axes.labelsize : 10.0
16 | axes.axisbelow : False
17 | axes.titlesize : 10.0
18 |
19 | xtick.top : True
20 | xtick.major.size : 5.0
21 | xtick.minor.size : 2.0
22 | xtick.major.width : 1.0
23 | xtick.minor.width : 0.5
24 | xtick.major.pad : 8.0
25 | xtick.minor.pad : 8.0
26 | xtick.direction : in
27 | xtick.minor.visible : True
28 | xtick.labelsize : 10.0
29 |
30 | ytick.right : True
31 | ytick.major.size : 5.0
32 | ytick.minor.size : 2.0
33 | ytick.major.width : 1.2
34 | ytick.minor.width : 0.6
35 | ytick.major.pad : 5.0
36 | ytick.minor.pad : 5.0
37 | ytick.direction : in
38 | ytick.minor.visible : True
39 | ytick.labelsize : 10.0
40 |
41 | legend.fontsize : 6.0
42 | legend.framealpha : 0.7
43 | legend.borderpad : 0.55
44 | legend.labelspacing : 0.25
45 | legend.handlelength : 1.0
46 | legend.handletextpad : 0.3
47 | legend.borderaxespad : 1.1
48 |
49 | savefig.pad_inches : 0
50 |
51 |
--------------------------------------------------------------------------------
/student_download/data/gd1_data.hdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/student_download/data/gd1_data.hdf
--------------------------------------------------------------------------------
/student_download/data/gd1_results.fits:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/student_download/data/gd1_results.fits
--------------------------------------------------------------------------------
/student_download/environment.yml:
--------------------------------------------------------------------------------
1 | name: AstronomicalData
2 | channels:
3 | - default
4 | - conda-forge
5 | - astropy
6 | dependencies:
7 | - python>=3.6
8 | - jupyter
9 | - numpy
10 | - matplotlib
11 | - pandas
12 | - pytables
13 | - h5py
14 | - scipy
15 | - astropy
16 | - astroquery
17 | - gala
18 |
--------------------------------------------------------------------------------
/student_download/episode_functions.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contains the functions that are defined throughout the episodes
3 | so that they can be imported as needed (especially between days of the workshop).
4 | """
5 | import numpy as np
6 | from matplotlib import pyplot as plt
7 |
8 | import astropy.units as u
9 | from astropy.coordinates import SkyCoord
10 | from gala.coordinates import GD1Koposov10, reflex_correct
11 |
12 | ##########################
13 | # Episode 2
14 | ##########################
15 | def make_rectangle(x1, x2, y1, y2):
16 | """Return the corners of a rectangle."""
17 | xs = [x1, x1, x2, x2, x1]
18 | ys = [y1, y2, y2, y1, y1]
19 | return xs, ys
20 |
21 | def skycoord_to_string(skycoord):
22 | """Convert a one-dimenstional list of SkyCoord to string for Gaia's query format."""
23 | corners_list_str = skycoord.to_string()
24 | corners_single_str = ' '.join(corners_list_str)
25 | return corners_single_str.replace(' ', ', ')
26 |
27 | ##########################
28 | # Episode 3
29 | ##########################
30 | def make_dataframe(table):
31 | """Transform coordinates from ICRS to GD-1 frame.
32 |
33 | table: Astropy Table
34 |
35 | returns: Pandas DataFrame
36 | """
37 | #Create a SkyCoord object with the coordinates and proper motions
38 | # in the input table
39 | skycoord = SkyCoord(
40 | ra=table['ra'],
41 | dec=table['dec'],
42 | pm_ra_cosdec=table['pmra'],
43 | pm_dec=table['pmdec'],
44 | distance=8*u.kpc,
45 | radial_velocity=0*u.km/u.s)
46 |
47 | # Define the GD-1 reference frame
48 | gd1_frame = GD1Koposov10()
49 |
50 | # Transform input coordinates to the GD-1 reference frame
51 | transformed = skycoord.transform_to(gd1_frame)
52 |
53 | # Correct GD-1 coordinates for solar system motion around galactic center
54 | skycoord_gd1 = reflex_correct(transformed)
55 |
56 | #Add GD-1 reference frame columns for coordinates and proper motions
57 | table['phi1'] = skycoord_gd1.phi1
58 | table['phi2'] = skycoord_gd1.phi2
59 | table['pm_phi1'] = skycoord_gd1.pm_phi1_cosphi2
60 | table['pm_phi2'] = skycoord_gd1.pm_phi2
61 |
62 | # Create DataFrame
63 | df = table.to_pandas()
64 |
65 | return df
66 |
67 | ##########################
68 | # Episode 4
69 | ##########################
70 | def plot_proper_motion(df):
71 | """Plot proper motion.
72 |
73 | df: DataFrame with `pm_phi1` and `pm_phi2`
74 | """
75 | x = df['pm_phi1']
76 | y = df['pm_phi2']
77 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
78 |
79 | plt.xlabel('Proper motion phi1 (mas/yr)')
80 | plt.ylabel('Proper motion phi2 (mas/yr)')
81 |
82 | plt.xlim(-12, 8)
83 | plt.ylim(-10, 10)
84 |
85 | def between(series, low, high):
86 | """Check whether values are between `low` and `high`."""
87 | return (series > low) & (series < high)
88 |
89 | def plot_pm_selection(df):
90 | x = df['phi1']
91 | y = df['phi2']
92 |
93 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
94 |
95 | plt.xlabel('phi1 [deg]')
96 | plt.ylabel('phi2 [deg]')
97 | plt.title('Proper motion selection', fontsize='medium')
98 |
99 | plt.axis('equal')
100 |
101 | ##########################
102 | # Episode 7
103 | ##########################
104 |
105 | def plot_cmd(dataframe):
106 | """Plot a color magnitude diagram.
107 |
108 | dataframe: DataFrame or Table with photometry data
109 | """
110 | y = dataframe['g_mean_psf_mag']
111 | x = dataframe['g_mean_psf_mag'] - dataframe['i_mean_psf_mag']
112 |
113 | plt.plot(x, y, 'ko', markersize=0.3, alpha=0.3)
114 |
115 | plt.xlim([0, 1.5])
116 | plt.ylim([22, 14])
117 |
118 | plt.ylabel('Magnitude (g)')
119 | plt.xlabel('Color (g-i)')
120 |
121 | def plot_cmd_selection(df):
122 | x = df['phi1']
123 | y = df['phi2']
124 |
125 | plt.plot(x, y, 'ko', markersize=0.7, alpha=0.9)
126 |
127 | plt.xlabel('$\phi_1$ [deg]')
128 | plt.ylabel('$\phi_2$ [deg]')
129 | plt.title('Proper motion + photometry selection', fontsize='medium')
130 |
131 | plt.axis('equal')
132 |
--------------------------------------------------------------------------------
/student_download/gd1_isochrone.hdf5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datacarpentry/astronomy-python/f5f052818bbdfc3fbde22da183c500c0696e1596/student_download/gd1_isochrone.hdf5
--------------------------------------------------------------------------------
/student_download/test_setup.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Data Carpentry Foundations of Astronomical Data Science Workshop\n",
8 | "\n",
9 | "Please read through and run this notebook before the workshop. There are two sections:\n",
10 | "\n",
11 | "1. A short introduction to Jupyter, with pointers to more resources.\n",
12 | "\n",
13 | "2. `import` statements to check whether you have everything installed that we need."
14 | ]
15 | },
16 | {
17 | "cell_type": "markdown",
18 | "metadata": {},
19 | "source": [
20 | "## Introduction to Jupyter\n",
21 | "\n",
22 | "This is a Jupyter notebook, which is a computational document that contains text, code, and results.\n",
23 | "\n",
24 | "There are several development environments you can use to work with notebooks. Currently the two most common are:\n",
25 | "\n",
26 | "* Jupyter Classic Notebook, and\n",
27 | "\n",
28 | "* JupyterLab, which is a newer environment with some improved features, but also some limitations. \n",
29 | "\n",
30 | "During the workshop, we will use the Classic Notebook environment. If you are new to Jupyter, we suggest you should, too.\n",
31 | "\n",
32 | "If you are familiar with JupyterLab and you would rather use it for the workshop, that is fine. Just be aware that there will be differences between your environment and ours.\n",
33 | "\n",
34 | "The following sections introduce the features you will need for the workshop.\n",
35 | "\n",
36 | "If you are new to Jupyter, you can [read about it here](https://jupyter.org/try) and follow the tutorial called \"Try Classic Notebook\". You also might like [this tutorial from DataQuest](https://www.dataquest.io/blog/jupyter-notebook-tutorial/)."
37 | ]
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {},
42 | "source": [
43 | "### Selecting and running cells\n",
44 | "\n",
45 | "Notebooks are divided into cells that contain either text or code.\n",
46 | "\n",
47 | "This cell is text; the following cell is code:"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": 1,
53 | "metadata": {
54 | "scrolled": true
55 | },
56 | "outputs": [
57 | {
58 | "name": "stdout",
59 | "output_type": "stream",
60 | "text": [
61 | "Hello\n"
62 | ]
63 | }
64 | ],
65 | "source": [
66 | "print('Hello')"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "To select a cell, click in the left margin next to the cell. You should see a blue frame surrounding the selected cell.\n",
74 | "\n",
75 | "To edit a code cell, click inside the cell. You should see a green frame around the selected cell, and you should see a cursor inside the cell.\n",
76 | "\n",
77 | "To edit a text cell, double-click inside the cell. You should see a green frame around the selected cell, and you should see a cursor inside the cell.\n",
78 | "\n",
79 | "Text cells use the Markdown typesetting language, which [you can read about here](https://www.markdownguide.org/).\n",
80 | "\n",
81 | "To run a cell, hold down Shift and press Enter.\n",
82 | "\n",
83 | "* If you run a text cell, Jupyter formats the text and displays the result.\n",
84 | "\n",
85 | "* If you run a code cell, Jupyter runs the code in the cell and displays the result, if any.\n",
86 | "\n",
87 | "To try it out, select the previous code cell and press Shift-Enter. It should run the code and print `Hello`.\n",
88 | "\n",
89 | "Then edit this cell, change some of the text, and press Shift-Enter to format it."
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "metadata": {},
95 | "source": [
96 | "### Adding and removing cells\n",
97 | "\n",
98 | "You can add and remove cells from a notebook using the menu at the top of the page and the tool bar below the menu.\n",
99 | "\n",
100 | "Try the following exercises:\n",
101 | "\n",
102 | "1. From the Insert menu, select \"Insert cell below\" to add a cell below this one. By default, you get a code cell, as you can see in the pull-down menu that says \"Code\".\n",
103 | "\n",
104 | "2. In the new cell, add a line of Python code and run it.\n",
105 | "\n",
106 | "3. Add another cell, select the new cell, and then click on the pull-down menu that says \"Code\". Select \"Markdown\". This makes the new cell a text cell.\n",
107 | "\n",
108 | "4. In the new text cell, type some text and format it.\n",
109 | "\n",
110 | "5. Use the arrow buttons in the tool bar to move cells up and down.\n",
111 | "\n",
112 | "6. Use the cut, copy, and paste buttons to delete, add, and move cells.\n",
113 | "\n",
114 | "As you make changes, Jupyter saves your notebook automatically, but if you want to make sure, you can press the save button, which looks like a floppy disk from the 1990s.\n",
115 | "\n",
116 | "Finally, when you are done with a notebook, select \"Close and Halt\" from the File menu."
117 | ]
118 | },
119 | {
120 | "cell_type": "markdown",
121 | "metadata": {},
122 | "source": [
123 | "## Check your installation\n",
124 | "\n",
125 | "Run the following cells to import the libraries we need for the workshop.\n",
126 | "\n",
127 | "If any of the libraries are missing, you will get an error message.\n",
128 | "\n",
129 | "If you don't get any error messages, you are ready for the workshop!"
130 | ]
131 | },
132 | {
133 | "cell_type": "code",
134 | "execution_count": 2,
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "import pandas as pd\n",
139 | "import numpy as np"
140 | ]
141 | },
142 | {
143 | "cell_type": "markdown",
144 | "metadata": {},
145 | "source": [
146 | "Run the following cell to make sure you have a recent version of Pandas. If you get an error, you probably have an old version of Pandas. Please update it before the workshop."
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": 3,
152 | "metadata": {},
153 | "outputs": [
154 | {
155 | "data": {
156 | "text/plain": [
157 | "array([0])"
158 | ]
159 | },
160 | "execution_count": 3,
161 | "metadata": {},
162 | "output_type": "execute_result"
163 | }
164 | ],
165 | "source": [
166 | "pd.Series([0]).to_numpy()"
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": 4,
172 | "metadata": {},
173 | "outputs": [],
174 | "source": [
175 | "import matplotlib as mpl\n",
176 | "import matplotlib.pyplot as plt\n",
177 | "from matplotlib.path import Path\n",
178 | "from matplotlib.patches import Polygon"
179 | ]
180 | },
181 | {
182 | "cell_type": "code",
183 | "execution_count": 5,
184 | "metadata": {},
185 | "outputs": [],
186 | "source": [
187 | "import astropy.coordinates as coord\n",
188 | "import astropy.units as u\n",
189 | "from astropy.table import Table"
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "execution_count": 6,
195 | "metadata": {},
196 | "outputs": [],
197 | "source": [
198 | "import gala.coordinates as gc"
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": 7,
204 | "metadata": {},
205 | "outputs": [
206 | {
207 | "name": "stdout",
208 | "output_type": "stream",
209 | "text": [
210 | "Created TAP+ (v20200428.1) - Connection:\n",
211 | "\tHost: gea.esac.esa.int\n",
212 | "\tUse HTTPS: True\n",
213 | "\tPort: 443\n",
214 | "\tSSL Port: 443\n",
215 | "Created TAP+ (v20200428.1) - Connection:\n",
216 | "\tHost: gea.esac.esa.int\n",
217 | "\tUse HTTPS: True\n",
218 | "\tPort: 443\n",
219 | "\tSSL Port: 443\n"
220 | ]
221 | }
222 | ],
223 | "source": [
224 | "# Note: running this import statement opens a connection\n",
225 | "# to a Gaia server, so it will fail if you are not connected\n",
226 | "# to the internet.\n",
227 | "\n",
228 | "from astroquery.gaia import Gaia"
229 | ]
230 | },
231 | {
232 | "cell_type": "code",
233 | "execution_count": 8,
234 | "metadata": {},
235 | "outputs": [],
236 | "source": [
237 | "from episode_functions import *"
238 | ]
239 | },
240 | {
241 | "cell_type": "code",
242 | "execution_count": 9,
243 | "metadata": {},
244 | "outputs": [],
245 | "source": [
246 | "isochrone = pd.read_hdf('gd1_isochrone.hdf5')"
247 | ]
248 | },
249 | {
250 | "cell_type": "markdown",
251 | "metadata": {},
252 | "source": [
253 | "## All done!\n",
254 | "\n",
255 | "Please contact your instructors if you experience any problems with these installation instructions. If you are working through these materials independently, let us know about any problems you encounter by [filing an issue on the lesson's GitHub repository](https://github.com/datacarpentry/astronomy-python/issues) or emailing team@carpentries.org."
256 | ]
257 | }
258 | ],
259 | "metadata": {
260 | "kernelspec": {
261 | "display_name": "Python (AstronomicalData)",
262 | "language": "python",
263 | "name": "astronomicaldata"
264 | },
265 | "language_info": {
266 | "codemirror_mode": {
267 | "name": "ipython",
268 | "version": 3
269 | },
270 | "file_extension": ".py",
271 | "mimetype": "text/x-python",
272 | "name": "python",
273 | "nbconvert_exporter": "python",
274 | "pygments_lexer": "ipython3",
275 | "version": "3.9.6"
276 | }
277 | },
278 | "nbformat": 4,
279 | "nbformat_minor": 4
280 | }
281 |
--------------------------------------------------------------------------------