├── .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 | ![Graph representation of a pull request](https://carpentries.github.io/sandpaper/articles/img/pr-flow.dot.svg) 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 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6419749.svg)](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 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6419749.svg)](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 ![good\_first\_issue](https://img.shields.io/badge/-good%20first%20issue-gold.svg). 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 | Scatter of proper motion phi1 versus phi2 showing overdensity in negative proper motions of GD-1 stars. 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 | ![](fig/04-motion_files/04-motion_67_0.png){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 | ![](fig/04-motion_files/04-motion_69_0.png){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 | ![](fig/gd1-4.png){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 | ![](fig/04-motion_files/04-motion_92_0.png){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 | Scatter plot of proper motion with overlaid polygon showing overdense region selected for analysis in Price-Whelan and Bonaca paper. 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 | ![](fig/04-motion_files/04-motion_100_0.png){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 | ![](fig/04-motion_files/04-motion_110_0.png){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 | ![](fig/04-motion_files/04-motion_plot_pm_selection.png){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 | ![](fig/05-select_files/05-select_14_0.png){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 | ![](fig/05-select_files/05-select_16_0.png){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 | ![](fig/05-select_files/05-select_66_0.png){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 | ![](fig/05-select_files/05-select_72_0.png){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 | Figure from Price-Whelan and Bonaca paper showing phi1 vs phi2 in GD-1 after selecting on proper motion. 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 | Figure from Price-Whelan and Bonaca paper showing phi1 vs phi2 in GD-1 after selecting on proper motion and photometry. 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 | Color-magnitude diagram for the stars selected based on proper motion, from Price-Whelan and Bonaca paper. 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 | Color-magnitude diagram for the stars selected based on proper motion, from Price-Whelan and Bonaca paper. 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 | ![](fig/07-photo_files/07-cmd_no_lims.png){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 | ![](fig/07-photo_files/07-cmd_lims.png){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 | ![](fig/07-photo_files/07-photo_12_0.png){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 | ![](fig/07-photo_files/07-photo_52_0.png){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 | ![](fig/07-photo_files/07-photo_62_0.png){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 | ![](fig/07-photo_files/07-photo_70_0.png){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 | ![](fig/07-photo_files/07-photo_91_0.png){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 | ![](fig/08-plot_files/08-plot_13_0.png){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 | ![](fig/08-plot_files/08-plot_13_0.png){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 | ![](fig/07-photo_files/07-photo_42_0.png){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 | --------------------------------------------------------------------------------