diff --git a/_preview/419/.buildinfo b/_preview/419/.buildinfo deleted file mode 100644 index 1b26c5e1e..000000000 --- a/_preview/419/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 19e06dbb963c53e6edd11a762ea068f2 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/_preview/419/_images/00097b2f8b8c2cd2be548a4a2abac5d778b43607c1c6229ad0d72d332cc801d1.png b/_preview/419/_images/00097b2f8b8c2cd2be548a4a2abac5d778b43607c1c6229ad0d72d332cc801d1.png deleted file mode 100644 index b6bd3a683..000000000 Binary files a/_preview/419/_images/00097b2f8b8c2cd2be548a4a2abac5d778b43607c1c6229ad0d72d332cc801d1.png and /dev/null differ diff --git a/_preview/419/_images/01e31da42bf1fe6a73bc4223fe1645048d12e4d175d92522b1bf662052ea7cd3.png b/_preview/419/_images/01e31da42bf1fe6a73bc4223fe1645048d12e4d175d92522b1bf662052ea7cd3.png deleted file mode 100644 index c485b82b1..000000000 Binary files a/_preview/419/_images/01e31da42bf1fe6a73bc4223fe1645048d12e4d175d92522b1bf662052ea7cd3.png and /dev/null differ diff --git a/_preview/419/_images/0625e346565833288d173c5d356f8fe736cf6f11ee8f2d22dfd533daea8e149d.png b/_preview/419/_images/0625e346565833288d173c5d356f8fe736cf6f11ee8f2d22dfd533daea8e149d.png deleted file mode 100644 index 0295602ce..000000000 Binary files a/_preview/419/_images/0625e346565833288d173c5d356f8fe736cf6f11ee8f2d22dfd533daea8e149d.png and /dev/null differ diff --git a/_preview/419/_images/079756d879cd417fc7801df083ca25cd2f399a4e449609b9a4d4dca2f818d896.png b/_preview/419/_images/079756d879cd417fc7801df083ca25cd2f399a4e449609b9a4d4dca2f818d896.png deleted file mode 100644 index 97ff7ef4d..000000000 Binary files a/_preview/419/_images/079756d879cd417fc7801df083ca25cd2f399a4e449609b9a4d4dca2f818d896.png and /dev/null differ diff --git a/_preview/419/_images/08ac028742a31a38fadffc5e2aea28dbe25d2e676258735095c5dbc783a5dad0.png b/_preview/419/_images/08ac028742a31a38fadffc5e2aea28dbe25d2e676258735095c5dbc783a5dad0.png deleted file mode 100644 index 73ca1f8fd..000000000 Binary files a/_preview/419/_images/08ac028742a31a38fadffc5e2aea28dbe25d2e676258735095c5dbc783a5dad0.png and /dev/null differ diff --git a/_preview/419/_images/0a92ae20d4d956fd7e5b402e961247f0b1a8ec92a6e0a85ab2bb61a51765799b.png b/_preview/419/_images/0a92ae20d4d956fd7e5b402e961247f0b1a8ec92a6e0a85ab2bb61a51765799b.png deleted file mode 100644 index 27fa2846c..000000000 Binary files a/_preview/419/_images/0a92ae20d4d956fd7e5b402e961247f0b1a8ec92a6e0a85ab2bb61a51765799b.png and /dev/null differ diff --git a/_preview/419/_images/0bab2f849a072a2d2afb9b00c213e1d076a475795bf2691d1279154d2791e207.png b/_preview/419/_images/0bab2f849a072a2d2afb9b00c213e1d076a475795bf2691d1279154d2791e207.png deleted file mode 100644 index 2964ac217..000000000 Binary files a/_preview/419/_images/0bab2f849a072a2d2afb9b00c213e1d076a475795bf2691d1279154d2791e207.png and /dev/null differ diff --git a/_preview/419/_images/0c416b36effb7b9b9d6818d0d0e4a56fb3406d6680b52df7ce3f36906ef429ea.png b/_preview/419/_images/0c416b36effb7b9b9d6818d0d0e4a56fb3406d6680b52df7ce3f36906ef429ea.png deleted file mode 100644 index 40b44f971..000000000 Binary files a/_preview/419/_images/0c416b36effb7b9b9d6818d0d0e4a56fb3406d6680b52df7ce3f36906ef429ea.png and /dev/null differ diff --git a/_preview/419/_images/1-gitstatus.png b/_preview/419/_images/1-gitstatus.png deleted file mode 100644 index 8cf630929..000000000 Binary files a/_preview/419/_images/1-gitstatus.png and /dev/null differ diff --git a/_preview/419/_images/10-github-newbranch.png b/_preview/419/_images/10-github-newbranch.png deleted file mode 100644 index b80884a28..000000000 Binary files a/_preview/419/_images/10-github-newbranch.png and /dev/null differ diff --git a/_preview/419/_images/11-newbranch-contribute.png b/_preview/419/_images/11-newbranch-contribute.png deleted file mode 100644 index 86b558073..000000000 Binary files a/_preview/419/_images/11-newbranch-contribute.png and /dev/null differ diff --git a/_preview/419/_images/1198a97c57a1fee9f869d7fd1e711eeb906948d4e17a602d8745405c27a71570.png b/_preview/419/_images/1198a97c57a1fee9f869d7fd1e711eeb906948d4e17a602d8745405c27a71570.png deleted file mode 100644 index 58db66037..000000000 Binary files a/_preview/419/_images/1198a97c57a1fee9f869d7fd1e711eeb906948d4e17a602d8745405c27a71570.png and /dev/null differ diff --git a/_preview/419/_images/12-compare.png b/_preview/419/_images/12-compare.png deleted file mode 100644 index 940e45dc0..000000000 Binary files a/_preview/419/_images/12-compare.png and /dev/null differ diff --git a/_preview/419/_images/13-message.png b/_preview/419/_images/13-message.png deleted file mode 100644 index 857887dbd..000000000 Binary files a/_preview/419/_images/13-message.png and /dev/null differ diff --git a/_preview/419/_images/14-prsummary.png b/_preview/419/_images/14-prsummary.png deleted file mode 100644 index 395783b15..000000000 Binary files a/_preview/419/_images/14-prsummary.png and /dev/null differ diff --git a/_preview/419/_images/15-todraft.png b/_preview/419/_images/15-todraft.png deleted file mode 100644 index e02e9eb9e..000000000 Binary files a/_preview/419/_images/15-todraft.png and /dev/null differ diff --git a/_preview/419/_images/16-draft.png b/_preview/419/_images/16-draft.png deleted file mode 100644 index e4cb69e14..000000000 Binary files a/_preview/419/_images/16-draft.png and /dev/null differ diff --git a/_preview/419/_images/17-fileschanged.png b/_preview/419/_images/17-fileschanged.png deleted file mode 100644 index dfcd6a169..000000000 Binary files a/_preview/419/_images/17-fileschanged.png and /dev/null differ diff --git a/_preview/419/_images/18-review.png b/_preview/419/_images/18-review.png deleted file mode 100644 index 626716a1a..000000000 Binary files a/_preview/419/_images/18-review.png and /dev/null differ diff --git a/_preview/419/_images/18cef30c144b43ab6ee933d735fa63322c833299f5f94b3111aefe0109bd3278.png b/_preview/419/_images/18cef30c144b43ab6ee933d735fa63322c833299f5f94b3111aefe0109bd3278.png deleted file mode 100644 index ae4bae8c4..000000000 Binary files a/_preview/419/_images/18cef30c144b43ab6ee933d735fa63322c833299f5f94b3111aefe0109bd3278.png and /dev/null differ diff --git a/_preview/419/_images/191a629919dca08953b893b3badbb7229c6449c29760753df90af612bb06f74a.png b/_preview/419/_images/191a629919dca08953b893b3badbb7229c6449c29760753df90af612bb06f74a.png deleted file mode 100644 index 7698aabb3..000000000 Binary files a/_preview/419/_images/191a629919dca08953b893b3badbb7229c6449c29760753df90af612bb06f74a.png and /dev/null differ diff --git a/_preview/419/_images/1b035ecaa9cd12c78098c6e73d3083bf3392010eb657fc0f6d9faeaee7f63bf4.png b/_preview/419/_images/1b035ecaa9cd12c78098c6e73d3083bf3392010eb657fc0f6d9faeaee7f63bf4.png deleted file mode 100644 index ec95209d5..000000000 Binary files a/_preview/419/_images/1b035ecaa9cd12c78098c6e73d3083bf3392010eb657fc0f6d9faeaee7f63bf4.png and /dev/null differ diff --git a/_preview/419/_images/1ff05bdb2e3b4f454f8f3c3ed26ba9cc262eab7ea28e8c1654de8b5cbc54d4d2.png b/_preview/419/_images/1ff05bdb2e3b4f454f8f3c3ed26ba9cc262eab7ea28e8c1654de8b5cbc54d4d2.png deleted file mode 100644 index 4cb7cc1bb..000000000 Binary files a/_preview/419/_images/1ff05bdb2e3b4f454f8f3c3ed26ba9cc262eab7ea28e8c1654de8b5cbc54d4d2.png and /dev/null differ diff --git a/_preview/419/_images/2-gitremote.png b/_preview/419/_images/2-gitremote.png deleted file mode 100644 index 004c982f6..000000000 Binary files a/_preview/419/_images/2-gitremote.png and /dev/null differ diff --git a/_preview/419/_images/20-green.png b/_preview/419/_images/20-green.png deleted file mode 100644 index 51068000e..000000000 Binary files a/_preview/419/_images/20-green.png and /dev/null differ diff --git a/_preview/419/_images/228ac1dc298116eb71ac8a7fbab76bffdb48ce6b78779bcc2c31837a01fcd6b5.png b/_preview/419/_images/228ac1dc298116eb71ac8a7fbab76bffdb48ce6b78779bcc2c31837a01fcd6b5.png deleted file mode 100644 index 9aa423638..000000000 Binary files a/_preview/419/_images/228ac1dc298116eb71ac8a7fbab76bffdb48ce6b78779bcc2c31837a01fcd6b5.png and /dev/null differ diff --git a/_preview/419/_images/2350da468e0aafc840b14986a53fc2d1fadf0666cc90ee622a8e1e00105e6cd7.png b/_preview/419/_images/2350da468e0aafc840b14986a53fc2d1fadf0666cc90ee622a8e1e00105e6cd7.png deleted file mode 100644 index f2dd62932..000000000 Binary files a/_preview/419/_images/2350da468e0aafc840b14986a53fc2d1fadf0666cc90ee622a8e1e00105e6cd7.png and /dev/null differ diff --git a/_preview/419/_images/28a10a4752ff52409d548e5325b9cd9ec1a653d170502898e66cf94b422de42f.png b/_preview/419/_images/28a10a4752ff52409d548e5325b9cd9ec1a653d170502898e66cf94b422de42f.png deleted file mode 100644 index f9fc46264..000000000 Binary files a/_preview/419/_images/28a10a4752ff52409d548e5325b9cd9ec1a653d170502898e66cf94b422de42f.png and /dev/null differ diff --git a/_preview/419/_images/2c9cc26aa429f9c024a5782a4345881da049b471feca4c6eae4ddf92bee3b3f1.png b/_preview/419/_images/2c9cc26aa429f9c024a5782a4345881da049b471feca4c6eae4ddf92bee3b3f1.png deleted file mode 100644 index 19f3b1dbc..000000000 Binary files a/_preview/419/_images/2c9cc26aa429f9c024a5782a4345881da049b471feca4c6eae4ddf92bee3b3f1.png and /dev/null differ diff --git a/_preview/419/_images/3-gitbranch.png b/_preview/419/_images/3-gitbranch.png deleted file mode 100644 index 96199c3e5..000000000 Binary files a/_preview/419/_images/3-gitbranch.png and /dev/null differ diff --git a/_preview/419/_images/3210548ea930f45d2c7e5191fe64f6b63bac227782c56ad6e968a3d44274d0a5.png b/_preview/419/_images/3210548ea930f45d2c7e5191fe64f6b63bac227782c56ad6e968a3d44274d0a5.png deleted file mode 100644 index f9950979c..000000000 Binary files a/_preview/419/_images/3210548ea930f45d2c7e5191fe64f6b63bac227782c56ad6e968a3d44274d0a5.png and /dev/null differ diff --git a/_preview/419/_images/3446d7febb92b54b0dfcce822462ede035bf572bce32c02b92e458d9fc33958e.png b/_preview/419/_images/3446d7febb92b54b0dfcce822462ede035bf572bce32c02b92e458d9fc33958e.png deleted file mode 100644 index 25c48f386..000000000 Binary files a/_preview/419/_images/3446d7febb92b54b0dfcce822462ede035bf572bce32c02b92e458d9fc33958e.png and /dev/null differ diff --git a/_preview/419/_images/3888f06f49b43a68a08a89039b581d771054ae22ed348bdd167f04deb15a1786.png b/_preview/419/_images/3888f06f49b43a68a08a89039b581d771054ae22ed348bdd167f04deb15a1786.png deleted file mode 100644 index fe422e9d2..000000000 Binary files a/_preview/419/_images/3888f06f49b43a68a08a89039b581d771054ae22ed348bdd167f04deb15a1786.png and /dev/null differ diff --git a/_preview/419/_images/3cd6d69dc83a2f6aa2262ba51fe6206c61067f9991da6a671ad8cab02787a894.png b/_preview/419/_images/3cd6d69dc83a2f6aa2262ba51fe6206c61067f9991da6a671ad8cab02787a894.png deleted file mode 100644 index d4e6b3d6d..000000000 Binary files a/_preview/419/_images/3cd6d69dc83a2f6aa2262ba51fe6206c61067f9991da6a671ad8cab02787a894.png and /dev/null differ diff --git a/_preview/419/_images/3d5792234feec41bc1d53c761f5d1b6b529723772e4b6cdfba5f56fbb9e1e59a.png b/_preview/419/_images/3d5792234feec41bc1d53c761f5d1b6b529723772e4b6cdfba5f56fbb9e1e59a.png deleted file mode 100644 index 08727c2f5..000000000 Binary files a/_preview/419/_images/3d5792234feec41bc1d53c761f5d1b6b529723772e4b6cdfba5f56fbb9e1e59a.png and /dev/null differ diff --git a/_preview/419/_images/4-gitnewbranch.png b/_preview/419/_images/4-gitnewbranch.png deleted file mode 100644 index 2fcb59285..000000000 Binary files a/_preview/419/_images/4-gitnewbranch.png and /dev/null differ diff --git a/_preview/419/_images/437f45eb897aa23ad39e8dea8db03b632cae1df0733aabfb52f5105748ba4d2c.png b/_preview/419/_images/437f45eb897aa23ad39e8dea8db03b632cae1df0733aabfb52f5105748ba4d2c.png deleted file mode 100644 index 7d828ea08..000000000 Binary files a/_preview/419/_images/437f45eb897aa23ad39e8dea8db03b632cae1df0733aabfb52f5105748ba4d2c.png and /dev/null differ diff --git a/_preview/419/_images/464f2151626b2d1163b904844680d14e39abf383bff9e2359b126f9da1f0a41d.png b/_preview/419/_images/464f2151626b2d1163b904844680d14e39abf383bff9e2359b126f9da1f0a41d.png deleted file mode 100644 index d973b9099..000000000 Binary files a/_preview/419/_images/464f2151626b2d1163b904844680d14e39abf383bff9e2359b126f9da1f0a41d.png and /dev/null differ diff --git a/_preview/419/_images/46ad4cc89c24786225e70dc43a5be6f1b6b4fd6a621e4f4da024f3db05409dd5.png b/_preview/419/_images/46ad4cc89c24786225e70dc43a5be6f1b6b4fd6a621e4f4da024f3db05409dd5.png deleted file mode 100644 index 273c69b0f..000000000 Binary files a/_preview/419/_images/46ad4cc89c24786225e70dc43a5be6f1b6b4fd6a621e4f4da024f3db05409dd5.png and /dev/null differ diff --git a/_preview/419/_images/4f37aea72431e50f7fe927d25944e7b69bbe6eeb3cd0e8353eb442ab8fc4553c.png b/_preview/419/_images/4f37aea72431e50f7fe927d25944e7b69bbe6eeb3cd0e8353eb442ab8fc4553c.png deleted file mode 100644 index 3a1f58fda..000000000 Binary files a/_preview/419/_images/4f37aea72431e50f7fe927d25944e7b69bbe6eeb3cd0e8353eb442ab8fc4553c.png and /dev/null differ diff --git a/_preview/419/_images/5-gitcheckout.png b/_preview/419/_images/5-gitcheckout.png deleted file mode 100644 index fa5871a38..000000000 Binary files a/_preview/419/_images/5-gitcheckout.png and /dev/null differ diff --git a/_preview/419/_images/509c90705817ac348925e3a22bcb7158d49e960c9c6314bef388658e0f72aacd.png b/_preview/419/_images/509c90705817ac348925e3a22bcb7158d49e960c9c6314bef388658e0f72aacd.png deleted file mode 100644 index 72ebf8542..000000000 Binary files a/_preview/419/_images/509c90705817ac348925e3a22bcb7158d49e960c9c6314bef388658e0f72aacd.png and /dev/null differ diff --git a/_preview/419/_images/50ac6e41733165de86b0c10d10b1ea9caa25a288943ce8cc785d8ffdc654eac7.png b/_preview/419/_images/50ac6e41733165de86b0c10d10b1ea9caa25a288943ce8cc785d8ffdc654eac7.png deleted file mode 100644 index 341aa9385..000000000 Binary files a/_preview/419/_images/50ac6e41733165de86b0c10d10b1ea9caa25a288943ce8cc785d8ffdc654eac7.png and /dev/null differ diff --git a/_preview/419/_images/513e649f9cb09ff7dd5df7ddab32340274bc98db99647c298f87225fcd29854c.png b/_preview/419/_images/513e649f9cb09ff7dd5df7ddab32340274bc98db99647c298f87225fcd29854c.png deleted file mode 100644 index 9c65e81f9..000000000 Binary files a/_preview/419/_images/513e649f9cb09ff7dd5df7ddab32340274bc98db99647c298f87225fcd29854c.png and /dev/null differ diff --git a/_preview/419/_images/519241a2201943d27209536f036ed151b63a528bca69610a530330d6b1ecc5b5.png b/_preview/419/_images/519241a2201943d27209536f036ed151b63a528bca69610a530330d6b1ecc5b5.png deleted file mode 100644 index 6786661d3..000000000 Binary files a/_preview/419/_images/519241a2201943d27209536f036ed151b63a528bca69610a530330d6b1ecc5b5.png and /dev/null differ diff --git a/_preview/419/_images/537f068f58fff14b531b3d040b26422c59c31388d3a46faccaee3b96749a2221.png b/_preview/419/_images/537f068f58fff14b531b3d040b26422c59c31388d3a46faccaee3b96749a2221.png deleted file mode 100644 index 64e865590..000000000 Binary files a/_preview/419/_images/537f068f58fff14b531b3d040b26422c59c31388d3a46faccaee3b96749a2221.png and /dev/null differ diff --git a/_preview/419/_images/5db4c4eb91eb2b15d051d432d790b744da1bc537940f29c09216d60aadbe3018.png b/_preview/419/_images/5db4c4eb91eb2b15d051d432d790b744da1bc537940f29c09216d60aadbe3018.png deleted file mode 100644 index e57ab3d88..000000000 Binary files a/_preview/419/_images/5db4c4eb91eb2b15d051d432d790b744da1bc537940f29c09216d60aadbe3018.png and /dev/null differ diff --git a/_preview/419/_images/5eb9e51893b4e3d241b23b56ffc5f0e0787c3e7731d8dad7c5c73674fa16bd9f.png b/_preview/419/_images/5eb9e51893b4e3d241b23b56ffc5f0e0787c3e7731d8dad7c5c73674fa16bd9f.png deleted file mode 100644 index 5fae4e9b7..000000000 Binary files a/_preview/419/_images/5eb9e51893b4e3d241b23b56ffc5f0e0787c3e7731d8dad7c5c73674fa16bd9f.png and /dev/null differ diff --git a/_preview/419/_images/6-samplechange.png b/_preview/419/_images/6-samplechange.png deleted file mode 100644 index 82f395a43..000000000 Binary files a/_preview/419/_images/6-samplechange.png and /dev/null differ diff --git a/_preview/419/_images/62ced909485cf68ae457c1551f4583f370bd3fc3eb78b98e3e656f1595b2a36d.png b/_preview/419/_images/62ced909485cf68ae457c1551f4583f370bd3fc3eb78b98e3e656f1595b2a36d.png deleted file mode 100644 index 62c3d3d1b..000000000 Binary files a/_preview/419/_images/62ced909485cf68ae457c1551f4583f370bd3fc3eb78b98e3e656f1595b2a36d.png and /dev/null differ diff --git a/_preview/419/_images/63cb5fc6111129f02f6f253c4f2d36aa03af181761194e9039182c1cc7fc528f.png b/_preview/419/_images/63cb5fc6111129f02f6f253c4f2d36aa03af181761194e9039182c1cc7fc528f.png deleted file mode 100644 index ed3c608f8..000000000 Binary files a/_preview/419/_images/63cb5fc6111129f02f6f253c4f2d36aa03af181761194e9039182c1cc7fc528f.png and /dev/null differ diff --git a/_preview/419/_images/6a-gitadd.png b/_preview/419/_images/6a-gitadd.png deleted file mode 100644 index 7bc8c1a1b..000000000 Binary files a/_preview/419/_images/6a-gitadd.png and /dev/null differ diff --git a/_preview/419/_images/6b-gitlog.png b/_preview/419/_images/6b-gitlog.png deleted file mode 100644 index 3afabd9d8..000000000 Binary files a/_preview/419/_images/6b-gitlog.png and /dev/null differ diff --git a/_preview/419/_images/6c-gitpush.png b/_preview/419/_images/6c-gitpush.png deleted file mode 100644 index c42bfefc5..000000000 Binary files a/_preview/419/_images/6c-gitpush.png and /dev/null differ diff --git a/_preview/419/_images/6ce189e2d9e1611badd2d85dbf550c29a26d309ae1572c1a9ec832183fc9c920.png b/_preview/419/_images/6ce189e2d9e1611badd2d85dbf550c29a26d309ae1572c1a9ec832183fc9c920.png deleted file mode 100644 index 51f3484f8..000000000 Binary files a/_preview/419/_images/6ce189e2d9e1611badd2d85dbf550c29a26d309ae1572c1a9ec832183fc9c920.png and /dev/null differ diff --git a/_preview/419/_images/6d-setupstream.png b/_preview/419/_images/6d-setupstream.png deleted file mode 100644 index f69688df5..000000000 Binary files a/_preview/419/_images/6d-setupstream.png and /dev/null differ diff --git a/_preview/419/_images/6d6769bc0f7eaf44e9f8d73df472fb292052c873c74f658fd8dd7864352a2619.png b/_preview/419/_images/6d6769bc0f7eaf44e9f8d73df472fb292052c873c74f658fd8dd7864352a2619.png deleted file mode 100644 index ad9dc1ad9..000000000 Binary files a/_preview/419/_images/6d6769bc0f7eaf44e9f8d73df472fb292052c873c74f658fd8dd7864352a2619.png and /dev/null differ diff --git a/_preview/419/_images/6dc37d2c1d495e52cb2d6c95854edff74ccf3bc1f5b4e25b17389e1965f48617.png b/_preview/419/_images/6dc37d2c1d495e52cb2d6c95854edff74ccf3bc1f5b4e25b17389e1965f48617.png deleted file mode 100644 index c9d12cbbe..000000000 Binary files a/_preview/419/_images/6dc37d2c1d495e52cb2d6c95854edff74ccf3bc1f5b4e25b17389e1965f48617.png and /dev/null differ diff --git a/_preview/419/_images/7-github-branchandstatus.png b/_preview/419/_images/7-github-branchandstatus.png deleted file mode 100644 index 7f81cc23b..000000000 Binary files a/_preview/419/_images/7-github-branchandstatus.png and /dev/null differ diff --git a/_preview/419/_images/75e950aecc9ff41c0670cd2c0ea2299592e350151976a680f3650234d9e01826.png b/_preview/419/_images/75e950aecc9ff41c0670cd2c0ea2299592e350151976a680f3650234d9e01826.png deleted file mode 100644 index 3eefbd51f..000000000 Binary files a/_preview/419/_images/75e950aecc9ff41c0670cd2c0ea2299592e350151976a680f3650234d9e01826.png and /dev/null differ diff --git a/_preview/419/_images/7a5aedf104c69c787c53494e8abe107fdeb84f088620539e6506ea35c494be42.png b/_preview/419/_images/7a5aedf104c69c787c53494e8abe107fdeb84f088620539e6506ea35c494be42.png deleted file mode 100644 index fb4e8256e..000000000 Binary files a/_preview/419/_images/7a5aedf104c69c787c53494e8abe107fdeb84f088620539e6506ea35c494be42.png and /dev/null differ diff --git a/_preview/419/_images/7bf5c29e9277f3e7aa7107c746f20adbef60e1e0540948820d6944d512d9bc9e.png b/_preview/419/_images/7bf5c29e9277f3e7aa7107c746f20adbef60e1e0540948820d6944d512d9bc9e.png deleted file mode 100644 index f832464c7..000000000 Binary files a/_preview/419/_images/7bf5c29e9277f3e7aa7107c746f20adbef60e1e0540948820d6944d512d9bc9e.png and /dev/null differ diff --git a/_preview/419/_images/7e19d5495940c0adbdd29975be3dff8c8b23bd5881bdca80a1fe0c559d828309.png b/_preview/419/_images/7e19d5495940c0adbdd29975be3dff8c8b23bd5881bdca80a1fe0c559d828309.png deleted file mode 100644 index a028b9e94..000000000 Binary files a/_preview/419/_images/7e19d5495940c0adbdd29975be3dff8c8b23bd5881bdca80a1fe0c559d828309.png and /dev/null differ diff --git a/_preview/419/_images/7e827ab6b1fe7eb8e999229e0d75ccaae3597c92ae7fbc243178901477e8ea9c.png b/_preview/419/_images/7e827ab6b1fe7eb8e999229e0d75ccaae3597c92ae7fbc243178901477e8ea9c.png deleted file mode 100644 index e114ca13f..000000000 Binary files a/_preview/419/_images/7e827ab6b1fe7eb8e999229e0d75ccaae3597c92ae7fbc243178901477e8ea9c.png and /dev/null differ diff --git a/_preview/419/_images/8-github.png b/_preview/419/_images/8-github.png deleted file mode 100644 index 2180ee7ca..000000000 Binary files a/_preview/419/_images/8-github.png and /dev/null differ diff --git a/_preview/419/_images/82af062524018f51120101e6a79930855059ebc0757960507c23b268176b24c6.png b/_preview/419/_images/82af062524018f51120101e6a79930855059ebc0757960507c23b268176b24c6.png deleted file mode 100644 index f49e06770..000000000 Binary files a/_preview/419/_images/82af062524018f51120101e6a79930855059ebc0757960507c23b268176b24c6.png and /dev/null differ diff --git a/_preview/419/_images/88b2bbd209d3812d32446e04ae2bc7ec6a9d0c9d76c64a8f56ad8304f51dd313.png b/_preview/419/_images/88b2bbd209d3812d32446e04ae2bc7ec6a9d0c9d76c64a8f56ad8304f51dd313.png deleted file mode 100644 index b93f191e9..000000000 Binary files a/_preview/419/_images/88b2bbd209d3812d32446e04ae2bc7ec6a9d0c9d76c64a8f56ad8304f51dd313.png and /dev/null differ diff --git a/_preview/419/_images/9-github-seebranches.png b/_preview/419/_images/9-github-seebranches.png deleted file mode 100644 index ac2096b09..000000000 Binary files a/_preview/419/_images/9-github-seebranches.png and /dev/null differ diff --git a/_preview/419/_images/922fa5c4d2d35f45f5d01ffc32579051402d6695654e0cdd9470766be3b1dce3.png b/_preview/419/_images/922fa5c4d2d35f45f5d01ffc32579051402d6695654e0cdd9470766be3b1dce3.png deleted file mode 100644 index 5cee9b1ad..000000000 Binary files a/_preview/419/_images/922fa5c4d2d35f45f5d01ffc32579051402d6695654e0cdd9470766be3b1dce3.png and /dev/null differ diff --git a/_preview/419/_images/92cee845d2b7db2398332636d7519f910cb20342b10768e48796ccf6604dfff0.png b/_preview/419/_images/92cee845d2b7db2398332636d7519f910cb20342b10768e48796ccf6604dfff0.png deleted file mode 100644 index f303d6e0a..000000000 Binary files a/_preview/419/_images/92cee845d2b7db2398332636d7519f910cb20342b10768e48796ccf6604dfff0.png and /dev/null differ diff --git a/_preview/419/_images/95ceb8b6dfa016b7190af29f5e5d3342eb8be7f1754bfaef4b214803c5beff69.png b/_preview/419/_images/95ceb8b6dfa016b7190af29f5e5d3342eb8be7f1754bfaef4b214803c5beff69.png deleted file mode 100644 index 0eebb50dc..000000000 Binary files a/_preview/419/_images/95ceb8b6dfa016b7190af29f5e5d3342eb8be7f1754bfaef4b214803c5beff69.png and /dev/null differ diff --git a/_preview/419/_images/96d227e0ce7f4673aa8f4f6a669afd6ad9ef7b90a5428f30126194e2caae2f1b.png b/_preview/419/_images/96d227e0ce7f4673aa8f4f6a669afd6ad9ef7b90a5428f30126194e2caae2f1b.png deleted file mode 100644 index 8c6c98ed7..000000000 Binary files a/_preview/419/_images/96d227e0ce7f4673aa8f4f6a669afd6ad9ef7b90a5428f30126194e2caae2f1b.png and /dev/null differ diff --git a/_preview/419/_images/971ed4b4413428bf02950c94e52aecf7252af060399486372f9f151b94447d11.png b/_preview/419/_images/971ed4b4413428bf02950c94e52aecf7252af060399486372f9f151b94447d11.png deleted file mode 100644 index d6fafebe8..000000000 Binary files a/_preview/419/_images/971ed4b4413428bf02950c94e52aecf7252af060399486372f9f151b94447d11.png and /dev/null differ diff --git a/_preview/419/_images/979af31bd706c31017650ee5fb727a56783b09c44f1a657c82b01830871d2fa7.png b/_preview/419/_images/979af31bd706c31017650ee5fb727a56783b09c44f1a657c82b01830871d2fa7.png deleted file mode 100644 index b1b67563a..000000000 Binary files a/_preview/419/_images/979af31bd706c31017650ee5fb727a56783b09c44f1a657c82b01830871d2fa7.png and /dev/null differ diff --git a/_preview/419/_images/99367f9cf2fb967975cd32eb969656dabdc05991adfe50f8db2f9f1c02f3ae96.png b/_preview/419/_images/99367f9cf2fb967975cd32eb969656dabdc05991adfe50f8db2f9f1c02f3ae96.png deleted file mode 100644 index 7ec0d65b9..000000000 Binary files a/_preview/419/_images/99367f9cf2fb967975cd32eb969656dabdc05991adfe50f8db2f9f1c02f3ae96.png and /dev/null differ diff --git a/_preview/419/_images/Anaconda.png b/_preview/419/_images/Anaconda.png deleted file mode 100644 index 7cb61c0f2..000000000 Binary files a/_preview/419/_images/Anaconda.png and /dev/null differ diff --git a/_preview/419/_images/Git-Logo-2Color.png b/_preview/419/_images/Git-Logo-2Color.png deleted file mode 100644 index 18c5b29d7..000000000 Binary files a/_preview/419/_images/Git-Logo-2Color.png and /dev/null differ diff --git a/_preview/419/_images/GitHub-logo.png b/_preview/419/_images/GitHub-logo.png deleted file mode 100644 index c9904ee88..000000000 Binary files a/_preview/419/_images/GitHub-logo.png and /dev/null differ diff --git a/_preview/419/_images/GitHubContrChecks.png b/_preview/419/_images/GitHubContrChecks.png deleted file mode 100644 index 9677152e9..000000000 Binary files a/_preview/419/_images/GitHubContrChecks.png and /dev/null differ diff --git a/_preview/419/_images/GitHubContrFork.png b/_preview/419/_images/GitHubContrFork.png deleted file mode 100644 index ac6448e67..000000000 Binary files a/_preview/419/_images/GitHubContrFork.png and /dev/null differ diff --git a/_preview/419/_images/GitHubContrJupyterLab.png b/_preview/419/_images/GitHubContrJupyterLab.png deleted file mode 100644 index 577aaf911..000000000 Binary files a/_preview/419/_images/GitHubContrJupyterLab.png and /dev/null differ diff --git a/_preview/419/_images/GitHubContrPR.png b/_preview/419/_images/GitHubContrPR.png deleted file mode 100644 index ad4a0cd4f..000000000 Binary files a/_preview/419/_images/GitHubContrPR.png and /dev/null differ diff --git a/_preview/419/_images/GitHubContrXarray.png b/_preview/419/_images/GitHubContrXarray.png deleted file mode 100644 index 54b132484..000000000 Binary files a/_preview/419/_images/GitHubContrXarray.png and /dev/null differ diff --git a/_preview/419/_images/GitHubJoin.png b/_preview/419/_images/GitHubJoin.png deleted file mode 100644 index b92b9473f..000000000 Binary files a/_preview/419/_images/GitHubJoin.png and /dev/null differ diff --git a/_preview/419/_images/GitHubNumPy.png b/_preview/419/_images/GitHubNumPy.png deleted file mode 100644 index e14a69c48..000000000 Binary files a/_preview/419/_images/GitHubNumPy.png and /dev/null differ diff --git a/_preview/419/_images/GitHubPythiaDisc.png b/_preview/419/_images/GitHubPythiaDisc.png deleted file mode 100644 index cf401bc72..000000000 Binary files a/_preview/419/_images/GitHubPythiaDisc.png and /dev/null differ diff --git a/_preview/419/_images/GitHubPythiaDisc156.png b/_preview/419/_images/GitHubPythiaDisc156.png deleted file mode 100644 index 5c367c943..000000000 Binary files a/_preview/419/_images/GitHubPythiaDisc156.png and /dev/null differ diff --git a/_preview/419/_images/GitHubPythiaIssue144.png b/_preview/419/_images/GitHubPythiaIssue144.png deleted file mode 100644 index 90a9e7c87..000000000 Binary files a/_preview/419/_images/GitHubPythiaIssue144.png and /dev/null differ diff --git a/_preview/419/_images/GitHubPythiaIssues.png b/_preview/419/_images/GitHubPythiaIssues.png deleted file mode 100644 index 7f485f97e..000000000 Binary files a/_preview/419/_images/GitHubPythiaIssues.png and /dev/null differ diff --git a/_preview/419/_images/GitHubPythiaIssuesClosed.png b/_preview/419/_images/GitHubPythiaIssuesClosed.png deleted file mode 100644 index 4da651370..000000000 Binary files a/_preview/419/_images/GitHubPythiaIssuesClosed.png and /dev/null differ diff --git a/_preview/419/_images/GitHubPython.png b/_preview/419/_images/GitHubPython.png deleted file mode 100644 index bc2c17241..000000000 Binary files a/_preview/419/_images/GitHubPython.png and /dev/null differ diff --git a/_preview/419/_images/GitHubXarray.png b/_preview/419/_images/GitHubXarray.png deleted file mode 100644 index 0aceb0f31..000000000 Binary files a/_preview/419/_images/GitHubXarray.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_CodeClone.png b/_preview/419/_images/GitHub_CodeClone.png deleted file mode 100644 index 76f3583b2..000000000 Binary files a/_preview/419/_images/GitHub_CodeClone.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_CodeCloneHTTPS.png b/_preview/419/_images/GitHub_CodeCloneHTTPS.png deleted file mode 100644 index c3c495a74..000000000 Binary files a/_preview/419/_images/GitHub_CodeCloneHTTPS.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Fork.png b/_preview/419/_images/GitHub_Fork.png deleted file mode 100644 index 37e867d69..000000000 Binary files a/_preview/419/_images/GitHub_Fork.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_ForkBranch.png b/_preview/419/_images/GitHub_ForkBranch.png deleted file mode 100644 index 46842a6db..000000000 Binary files a/_preview/419/_images/GitHub_ForkBranch.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_ForkDest.png b/_preview/419/_images/GitHub_ForkDest.png deleted file mode 100644 index 6c9903b9a..000000000 Binary files a/_preview/419/_images/GitHub_ForkDest.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_ForkPost.png b/_preview/419/_images/GitHub_ForkPost.png deleted file mode 100644 index ac91196fd..000000000 Binary files a/_preview/419/_images/GitHub_ForkPost.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_RepoTools.png b/_preview/419/_images/GitHub_RepoTools.png deleted file mode 100644 index c1382ee3c..000000000 Binary files a/_preview/419/_images/GitHub_RepoTools.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_SandboxRepo.png b/_preview/419/_images/GitHub_SandboxRepo.png deleted file mode 100644 index 23595ba10..000000000 Binary files a/_preview/419/_images/GitHub_SandboxRepo.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Notification_Filter.png b/_preview/419/_images/GitHub_Setup_Advanced_Notification_Filter.png deleted file mode 100644 index f62a4feff..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Notification_Filter.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Notification_Settings.png b/_preview/419/_images/GitHub_Setup_Advanced_Notification_Settings.png deleted file mode 100644 index 7bae89e20..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Notification_Settings.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Notifications.png b/_preview/419/_images/GitHub_Setup_Advanced_Notifications.png deleted file mode 100644 index e797f7720..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Notifications.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Notifications_Browser.png b/_preview/419/_images/GitHub_Setup_Advanced_Notifications_Browser.png deleted file mode 100644 index 38bd48ff2..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Notifications_Browser.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Notifications_Unsubscribe.png b/_preview/419/_images/GitHub_Setup_Advanced_Notifications_Unsubscribe.png deleted file mode 100644 index b0e220199..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Notifications_Unsubscribe.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Unwatch.png b/_preview/419/_images/GitHub_Setup_Advanced_Unwatch.png deleted file mode 100644 index dce261fd2..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Unwatch.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Watch.png b/_preview/419/_images/GitHub_Setup_Advanced_Watch.png deleted file mode 100644 index 509121004..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Watch.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_Watch_All_Activity.png b/_preview/419/_images/GitHub_Setup_Advanced_Watch_All_Activity.png deleted file mode 100644 index d6caeefed..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_Watch_All_Activity.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_https_URL.png b/_preview/419/_images/GitHub_Setup_Advanced_https_URL.png deleted file mode 100644 index d13cd1b55..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_https_URL.png and /dev/null differ diff --git a/_preview/419/_images/GitHub_Setup_Advanced_ssh_URL.png b/_preview/419/_images/GitHub_Setup_Advanced_ssh_URL.png deleted file mode 100644 index 43b3767e7..000000000 Binary files a/_preview/419/_images/GitHub_Setup_Advanced_ssh_URL.png and /dev/null differ diff --git a/_preview/419/_images/NCAR-contemp-logo-blue.svg b/_preview/419/_images/NCAR-contemp-logo-blue.svg deleted file mode 100644 index 3bcda6351..000000000 --- a/_preview/419/_images/NCAR-contemp-logo-blue.svg +++ /dev/null @@ -1 +0,0 @@ -NCAR-contemp-logo-blue.a diff --git a/_preview/419/_images/ProjectPythia_Logo_Final-01-Blue.svg b/_preview/419/_images/ProjectPythia_Logo_Final-01-Blue.svg deleted file mode 100644 index 961efc26a..000000000 --- a/_preview/419/_images/ProjectPythia_Logo_Final-01-Blue.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/_preview/419/_images/ProjectPythia_Logo_Final-01-Blue1.svg b/_preview/419/_images/ProjectPythia_Logo_Final-01-Blue1.svg deleted file mode 100644 index 961efc26a..000000000 --- a/_preview/419/_images/ProjectPythia_Logo_Final-01-Blue1.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/_preview/419/_images/UAlbany-A2-logo-purple-gold.svg b/_preview/419/_images/UAlbany-A2-logo-purple-gold.svg deleted file mode 100644 index 4fdfe3a8e..000000000 --- a/_preview/419/_images/UAlbany-A2-logo-purple-gold.svg +++ /dev/null @@ -1,1125 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/_preview/419/_images/Unidata_logo_horizontal_1200x300.svg b/_preview/419/_images/Unidata_logo_horizontal_1200x300.svg deleted file mode 100644 index 0d9fd70fd..000000000 --- a/_preview/419/_images/Unidata_logo_horizontal_1200x300.svg +++ /dev/null @@ -1,891 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/_preview/419/_images/XarrayGithub.png b/_preview/419/_images/XarrayGithub.png deleted file mode 100644 index d8cc98a66..000000000 Binary files a/_preview/419/_images/XarrayGithub.png and /dev/null differ diff --git a/_preview/419/_images/a279d1c0e41635648d130b880f476c12b4bd699e2311f0f1db1e4c0fb29853e9.png b/_preview/419/_images/a279d1c0e41635648d130b880f476c12b4bd699e2311f0f1db1e4c0fb29853e9.png deleted file mode 100644 index dec498329..000000000 Binary files a/_preview/419/_images/a279d1c0e41635648d130b880f476c12b4bd699e2311f0f1db1e4c0fb29853e9.png and /dev/null differ diff --git a/_preview/419/_images/a526b1544f5a70f1d8ea9a6cebc23bf7da88a5425d98c9ad58be733784bd8ccc.png b/_preview/419/_images/a526b1544f5a70f1d8ea9a6cebc23bf7da88a5425d98c9ad58be733784bd8ccc.png deleted file mode 100644 index 262f3ce4e..000000000 Binary files a/_preview/419/_images/a526b1544f5a70f1d8ea9a6cebc23bf7da88a5425d98c9ad58be733784bd8ccc.png and /dev/null differ diff --git a/_preview/419/_images/accf7e6cbbf7180ed9f864595a0be49ef97ebb1d94b62dbbe885cd9711bb5d6c.png b/_preview/419/_images/accf7e6cbbf7180ed9f864595a0be49ef97ebb1d94b62dbbe885cd9711bb5d6c.png deleted file mode 100644 index 452c39035..000000000 Binary files a/_preview/419/_images/accf7e6cbbf7180ed9f864595a0be49ef97ebb1d94b62dbbe885cd9711bb5d6c.png and /dev/null differ diff --git a/_preview/419/_images/ae4954a2e3dafba65da6ea4c9d0c47ef4dd166e276c3167ffd19b32a1b0f3b23.png b/_preview/419/_images/ae4954a2e3dafba65da6ea4c9d0c47ef4dd166e276c3167ffd19b32a1b0f3b23.png deleted file mode 100644 index 6a6c12853..000000000 Binary files a/_preview/419/_images/ae4954a2e3dafba65da6ea4c9d0c47ef4dd166e276c3167ffd19b32a1b0f3b23.png and /dev/null differ diff --git a/_preview/419/_images/afaa1ad3f96880a24585ffffedda5c8e155befe28121c2d6674d7c6e778f867c.png b/_preview/419/_images/afaa1ad3f96880a24585ffffedda5c8e155befe28121c2d6674d7c6e778f867c.png deleted file mode 100644 index 6b118e16a..000000000 Binary files a/_preview/419/_images/afaa1ad3f96880a24585ffffedda5c8e155befe28121c2d6674d7c6e778f867c.png and /dev/null differ diff --git a/_preview/419/_images/array_index.png b/_preview/419/_images/array_index.png deleted file mode 100644 index d1da26020..000000000 Binary files a/_preview/419/_images/array_index.png and /dev/null differ diff --git a/_preview/419/_images/b1617f641117c983408fb2f3a9bf14ab617fc2f3f74dd24f4cd5a0867cdf2a85.png b/_preview/419/_images/b1617f641117c983408fb2f3a9bf14ab617fc2f3f74dd24f4cd5a0867cdf2a85.png deleted file mode 100644 index 1d30e1e9b..000000000 Binary files a/_preview/419/_images/b1617f641117c983408fb2f3a9bf14ab617fc2f3f74dd24f4cd5a0867cdf2a85.png and /dev/null differ diff --git a/_preview/419/_images/b375591b1592c2b318f5ebd705ef72a249ba47bdf76bdccb247330278b8aa1bf.png b/_preview/419/_images/b375591b1592c2b318f5ebd705ef72a249ba47bdf76bdccb247330278b8aa1bf.png deleted file mode 100644 index b2149f402..000000000 Binary files a/_preview/419/_images/b375591b1592c2b318f5ebd705ef72a249ba47bdf76bdccb247330278b8aa1bf.png and /dev/null differ diff --git a/_preview/419/_images/b4b780f606ceda586490a9cc3371de2a6bb0d63382deff50658a6d6e6624dea0.png b/_preview/419/_images/b4b780f606ceda586490a9cc3371de2a6bb0d63382deff50658a6d6e6624dea0.png deleted file mode 100644 index 3d76e4e3e..000000000 Binary files a/_preview/419/_images/b4b780f606ceda586490a9cc3371de2a6bb0d63382deff50658a6d6e6624dea0.png and /dev/null differ diff --git a/_preview/419/_images/b699d49a5bd056025b7e0fe8189789d4bbec17a114c88b378dc77f813d98bc60.png b/_preview/419/_images/b699d49a5bd056025b7e0fe8189789d4bbec17a114c88b378dc77f813d98bc60.png deleted file mode 100644 index 549fc1c25..000000000 Binary files a/_preview/419/_images/b699d49a5bd056025b7e0fe8189789d4bbec17a114c88b378dc77f813d98bc60.png and /dev/null differ diff --git a/_preview/419/_images/bdaf6a3686c82bc6b2f2854849a73d0134030236328f61b255c026816f52970c.png b/_preview/419/_images/bdaf6a3686c82bc6b2f2854849a73d0134030236328f61b255c026816f52970c.png deleted file mode 100644 index d0176a547..000000000 Binary files a/_preview/419/_images/bdaf6a3686c82bc6b2f2854849a73d0134030236328f61b255c026816f52970c.png and /dev/null differ diff --git a/_preview/419/_images/bdf1dc900425ff437651e1fbd01c7d350f664d2296806b6799b1d66f3963b5b0.png b/_preview/419/_images/bdf1dc900425ff437651e1fbd01c7d350f664d2296806b6799b1d66f3963b5b0.png deleted file mode 100644 index 74e965c8a..000000000 Binary files a/_preview/419/_images/bdf1dc900425ff437651e1fbd01c7d350f664d2296806b6799b1d66f3963b5b0.png and /dev/null differ diff --git a/_preview/419/_images/binder-highlight.png b/_preview/419/_images/binder-highlight.png deleted file mode 100644 index e52b2ab00..000000000 Binary files a/_preview/419/_images/binder-highlight.png and /dev/null differ diff --git a/_preview/419/_images/branching.gif b/_preview/419/_images/branching.gif deleted file mode 100644 index 31e66bdd0..000000000 Binary files a/_preview/419/_images/branching.gif and /dev/null differ diff --git a/_preview/419/_images/c.png b/_preview/419/_images/c.png deleted file mode 100644 index 913d56f11..000000000 Binary files a/_preview/419/_images/c.png and /dev/null differ diff --git a/_preview/419/_images/c016b1383793847705244cc4b8f1129f861e794226d30aebbaaa7556acab6487.png b/_preview/419/_images/c016b1383793847705244cc4b8f1129f861e794226d30aebbaaa7556acab6487.png deleted file mode 100644 index 8452d8d57..000000000 Binary files a/_preview/419/_images/c016b1383793847705244cc4b8f1129f861e794226d30aebbaaa7556acab6487.png and /dev/null differ diff --git a/_preview/419/_images/c4601c390e490f5dbe7cfaa0cfe024da3db6e9f5d9703e5590c05916251f48e0.png b/_preview/419/_images/c4601c390e490f5dbe7cfaa0cfe024da3db6e9f5d9703e5590c05916251f48e0.png deleted file mode 100644 index dd3f3b413..000000000 Binary files a/_preview/419/_images/c4601c390e490f5dbe7cfaa0cfe024da3db6e9f5d9703e5590c05916251f48e0.png and /dev/null differ diff --git a/_preview/419/_images/c481476eb1c4e8f74b4334133f26d23b9bb4fcb58fb6cd3527bf277318b10155.png b/_preview/419/_images/c481476eb1c4e8f74b4334133f26d23b9bb4fcb58fb6cd3527bf277318b10155.png deleted file mode 100644 index 314a44d04..000000000 Binary files a/_preview/419/_images/c481476eb1c4e8f74b4334133f26d23b9bb4fcb58fb6cd3527bf277318b10155.png and /dev/null differ diff --git a/_preview/419/_images/c593846a966886929bd8a0ccb6762de4a5e5595aca6e3a97d02a9206935feae9.png b/_preview/419/_images/c593846a966886929bd8a0ccb6762de4a5e5595aca6e3a97d02a9206935feae9.png deleted file mode 100644 index 45a4026ca..000000000 Binary files a/_preview/419/_images/c593846a966886929bd8a0ccb6762de4a5e5595aca6e3a97d02a9206935feae9.png and /dev/null differ diff --git a/_preview/419/_images/c5d0c83794ccb23ae5e78a3cae32d9cc17daa71567aeea7de8ff982b35d5a53a.png b/_preview/419/_images/c5d0c83794ccb23ae5e78a3cae32d9cc17daa71567aeea7de8ff982b35d5a53a.png deleted file mode 100644 index 6111d19bc..000000000 Binary files a/_preview/419/_images/c5d0c83794ccb23ae5e78a3cae32d9cc17daa71567aeea7de8ff982b35d5a53a.png and /dev/null differ diff --git a/_preview/419/_images/ca23e937b43bda17611e6fc03fb58db631f0f9ec48a32a5f4a2cc78290e9aec4.png b/_preview/419/_images/ca23e937b43bda17611e6fc03fb58db631f0f9ec48a32a5f4a2cc78290e9aec4.png deleted file mode 100644 index f2b620842..000000000 Binary files a/_preview/419/_images/ca23e937b43bda17611e6fc03fb58db631f0f9ec48a32a5f4a2cc78290e9aec4.png and /dev/null differ diff --git a/_preview/419/_images/cadbe3c8997ffc29b85b27fa7ff43559e9833077e27116a2d630d39bdf4d65f5.png b/_preview/419/_images/cadbe3c8997ffc29b85b27fa7ff43559e9833077e27116a2d630d39bdf4d65f5.png deleted file mode 100644 index f74b7881c..000000000 Binary files a/_preview/419/_images/cadbe3c8997ffc29b85b27fa7ff43559e9833077e27116a2d630d39bdf4d65f5.png and /dev/null differ diff --git a/_preview/419/_images/cartopy_logo.png b/_preview/419/_images/cartopy_logo.png deleted file mode 100644 index 6533e45d5..000000000 Binary files a/_preview/419/_images/cartopy_logo.png and /dev/null differ diff --git a/_preview/419/_images/cedcd403574b1a4da611754fd42bf6b165b8c6f822dc3d6f0293e404facd4597.png b/_preview/419/_images/cedcd403574b1a4da611754fd42bf6b165b8c6f822dc3d6f0293e404facd4597.png deleted file mode 100644 index f533175a7..000000000 Binary files a/_preview/419/_images/cedcd403574b1a4da611754fd42bf6b165b8c6f822dc3d6f0293e404facd4597.png and /dev/null differ diff --git a/_preview/419/_images/codecells.png b/_preview/419/_images/codecells.png deleted file mode 100644 index f6cd8774d..000000000 Binary files a/_preview/419/_images/codecells.png and /dev/null differ diff --git a/_preview/419/_images/console.png b/_preview/419/_images/console.png deleted file mode 100644 index 41d7e88d8..000000000 Binary files a/_preview/419/_images/console.png and /dev/null differ diff --git a/_preview/419/_images/cyclic.png b/_preview/419/_images/cyclic.png deleted file mode 100644 index 4d2704e5d..000000000 Binary files a/_preview/419/_images/cyclic.png and /dev/null differ diff --git a/_preview/419/_images/d.png b/_preview/419/_images/d.png deleted file mode 100644 index 8045141f2..000000000 Binary files a/_preview/419/_images/d.png and /dev/null differ diff --git a/_preview/419/_images/d24c8319bda1f0a93467f96b4fe912c9f7d8e0e83723198d736e4162da5d128b.png b/_preview/419/_images/d24c8319bda1f0a93467f96b4fe912c9f7d8e0e83723198d736e4162da5d128b.png deleted file mode 100644 index c99c6144a..000000000 Binary files a/_preview/419/_images/d24c8319bda1f0a93467f96b4fe912c9f7d8e0e83723198d736e4162da5d128b.png and /dev/null differ diff --git a/_preview/419/_images/dask_horizontal.svg b/_preview/419/_images/dask_horizontal.svg deleted file mode 100644 index 868fcfa34..000000000 --- a/_preview/419/_images/dask_horizontal.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/_preview/419/_images/dbbd81aa3ad7ceca6264932bbf192282e1e18aa8ee7e84dd27f0bf4c4b3eef6a.png b/_preview/419/_images/dbbd81aa3ad7ceca6264932bbf192282e1e18aa8ee7e84dd27f0bf4c4b3eef6a.png deleted file mode 100644 index 1c9c5f932..000000000 Binary files a/_preview/419/_images/dbbd81aa3ad7ceca6264932bbf192282e1e18aa8ee7e84dd27f0bf4c4b3eef6a.png and /dev/null differ diff --git a/_preview/419/_images/dc80aaee468d8d4a69860c573d1632781902fc13e95344e4d357dcd1f01b2370.png b/_preview/419/_images/dc80aaee468d8d4a69860c573d1632781902fc13e95344e4d357dcd1f01b2370.png deleted file mode 100644 index 9bfda65ec..000000000 Binary files a/_preview/419/_images/dc80aaee468d8d4a69860c573d1632781902fc13e95344e4d357dcd1f01b2370.png and /dev/null differ diff --git a/_preview/419/_images/dd9cf91449deae549bfb5900e1cd21fd6ec2b7fb47d2321bb9d7033c17209776.png b/_preview/419/_images/dd9cf91449deae549bfb5900e1cd21fd6ec2b7fb47d2321bb9d7033c17209776.png deleted file mode 100644 index 6223bf7eb..000000000 Binary files a/_preview/419/_images/dd9cf91449deae549bfb5900e1cd21fd6ec2b7fb47d2321bb9d7033c17209776.png and /dev/null differ diff --git a/_preview/419/_images/de77b23271c413fd74a3cd9a7941b7e14bf880ff2e9b7b4c80baf1471dc5796f.png b/_preview/419/_images/de77b23271c413fd74a3cd9a7941b7e14bf880ff2e9b7b4c80baf1471dc5796f.png deleted file mode 100644 index 2874ef177..000000000 Binary files a/_preview/419/_images/de77b23271c413fd74a3cd9a7941b7e14bf880ff2e9b7b4c80baf1471dc5796f.png and /dev/null differ diff --git a/_preview/419/_images/deletingbranch.gif b/_preview/419/_images/deletingbranch.gif deleted file mode 100644 index f290dd039..000000000 Binary files a/_preview/419/_images/deletingbranch.gif and /dev/null differ diff --git a/_preview/419/_images/diverging.png b/_preview/419/_images/diverging.png deleted file mode 100644 index 887c69121..000000000 Binary files a/_preview/419/_images/diverging.png and /dev/null differ diff --git a/_preview/419/_images/e0e69b46b2dd1d98ddc57b525c4acf912cdc2dbad326a7f0f98a4aba6e4927a5.png b/_preview/419/_images/e0e69b46b2dd1d98ddc57b525c4acf912cdc2dbad326a7f0f98a4aba6e4927a5.png deleted file mode 100644 index d6414c9f9..000000000 Binary files a/_preview/419/_images/e0e69b46b2dd1d98ddc57b525c4acf912cdc2dbad326a7f0f98a4aba6e4927a5.png and /dev/null differ diff --git a/_preview/419/_images/e3957a7246fb0d83acceefcb861ebea739d23ab070402577d93dcbddc04869e1.png b/_preview/419/_images/e3957a7246fb0d83acceefcb861ebea739d23ab070402577d93dcbddc04869e1.png deleted file mode 100644 index 166ffe59a..000000000 Binary files a/_preview/419/_images/e3957a7246fb0d83acceefcb861ebea739d23ab070402577d93dcbddc04869e1.png and /dev/null differ diff --git a/_preview/419/_images/e50e5648082ce22b52011d302a7cda36b0348198e192885034e1b4509ffbae6c.png b/_preview/419/_images/e50e5648082ce22b52011d302a7cda36b0348198e192885034e1b4509ffbae6c.png deleted file mode 100644 index a90075908..000000000 Binary files a/_preview/419/_images/e50e5648082ce22b52011d302a7cda36b0348198e192885034e1b4509ffbae6c.png and /dev/null differ diff --git a/_preview/419/_images/e603b7f2c8eb3176fe23317457122b7747bc039ce5c3196a519d2ae39489d14b.png b/_preview/419/_images/e603b7f2c8eb3176fe23317457122b7747bc039ce5c3196a519d2ae39489d14b.png deleted file mode 100644 index 61a4c5805..000000000 Binary files a/_preview/419/_images/e603b7f2c8eb3176fe23317457122b7747bc039ce5c3196a519d2ae39489d14b.png and /dev/null differ diff --git a/_preview/419/_images/e788d89759ef499556b8bd4e439e792ade760fd064e6bf130a341e772ee38cd5.png b/_preview/419/_images/e788d89759ef499556b8bd4e439e792ade760fd064e6bf130a341e772ee38cd5.png deleted file mode 100644 index 50fc4053a..000000000 Binary files a/_preview/419/_images/e788d89759ef499556b8bd4e439e792ade760fd064e6bf130a341e772ee38cd5.png and /dev/null differ diff --git a/_preview/419/_images/e835ed482076e53833ea5126dbb71e2f668a34e10a74fd8199204f24f9c90cb1.png b/_preview/419/_images/e835ed482076e53833ea5126dbb71e2f668a34e10a74fd8199204f24f9c90cb1.png deleted file mode 100644 index d13804aa9..000000000 Binary files a/_preview/419/_images/e835ed482076e53833ea5126dbb71e2f668a34e10a74fd8199204f24f9c90cb1.png and /dev/null differ diff --git a/_preview/419/_images/extensions.png b/_preview/419/_images/extensions.png deleted file mode 100644 index e0bdca191..000000000 Binary files a/_preview/419/_images/extensions.png and /dev/null differ diff --git a/_preview/419/_images/f15e5a34ed98fe236412f1bd9cd8acc64e0bcd569c7c06f4088e3233e385ff0a.png b/_preview/419/_images/f15e5a34ed98fe236412f1bd9cd8acc64e0bcd569c7c06f4088e3233e385ff0a.png deleted file mode 100644 index d2cc5db9e..000000000 Binary files a/_preview/419/_images/f15e5a34ed98fe236412f1bd9cd8acc64e0bcd569c7c06f4088e3233e385ff0a.png and /dev/null differ diff --git a/_preview/419/_images/f19db708fb5b9c4ba49d3d6dafb3274fe7de98450ed19dd44c22fc4019971c8e.png b/_preview/419/_images/f19db708fb5b9c4ba49d3d6dafb3274fe7de98450ed19dd44c22fc4019971c8e.png deleted file mode 100644 index 23ab8eef2..000000000 Binary files a/_preview/419/_images/f19db708fb5b9c4ba49d3d6dafb3274fe7de98450ed19dd44c22fc4019971c8e.png and /dev/null differ diff --git a/_preview/419/_images/f28790b92986c53664cd94ddbf27f0047969b83e9e257e3fdcaa799f1f4bf891.png b/_preview/419/_images/f28790b92986c53664cd94ddbf27f0047969b83e9e257e3fdcaa799f1f4bf891.png deleted file mode 100644 index 189dc6bc1..000000000 Binary files a/_preview/419/_images/f28790b92986c53664cd94ddbf27f0047969b83e9e257e3fdcaa799f1f4bf891.png and /dev/null differ diff --git a/_preview/419/_images/f7b90da02ddccafe6b7c7ed65b86dea5a12c32741b9e779c00c980c289e6c7af.png b/_preview/419/_images/f7b90da02ddccafe6b7c7ed65b86dea5a12c32741b9e779c00c980c289e6c7af.png deleted file mode 100644 index c134e6f6d..000000000 Binary files a/_preview/419/_images/f7b90da02ddccafe6b7c7ed65b86dea5a12c32741b9e779c00c980c289e6c7af.png and /dev/null differ diff --git a/_preview/419/_images/f84e83601383c87e54c66fd6f9ec9b7603766c645d698b7adc2f3676e4661c39.png b/_preview/419/_images/f84e83601383c87e54c66fd6f9ec9b7603766c645d698b7adc2f3676e4661c39.png deleted file mode 100644 index a58e4167d..000000000 Binary files a/_preview/419/_images/f84e83601383c87e54c66fd6f9ec9b7603766c645d698b7adc2f3676e4661c39.png and /dev/null differ diff --git a/_preview/419/_images/f88ff83440607b4ed1d0eb0e919080ab0c192b8feadd08b2ee6bced042228bcc.png b/_preview/419/_images/f88ff83440607b4ed1d0eb0e919080ab0c192b8feadd08b2ee6bced042228bcc.png deleted file mode 100644 index e896ef150..000000000 Binary files a/_preview/419/_images/f88ff83440607b4ed1d0eb0e919080ab0c192b8feadd08b2ee6bced042228bcc.png and /dev/null differ diff --git a/_preview/419/_images/f9037e8013e3e2b21587a371853bb666704b3c8c4de15e6827c9ba22b871d1ba.png b/_preview/419/_images/f9037e8013e3e2b21587a371853bb666704b3c8c4de15e6827c9ba22b871d1ba.png deleted file mode 100644 index cb2da4d3d..000000000 Binary files a/_preview/419/_images/f9037e8013e3e2b21587a371853bb666704b3c8c4de15e6827c9ba22b871d1ba.png and /dev/null differ diff --git a/_preview/419/_images/fbfaaaebf1974140e69d13218d4a8a939db0f64462fc826d3ce36c144c84077c.png b/_preview/419/_images/fbfaaaebf1974140e69d13218d4a8a939db0f64462fc826d3ce36c144c84077c.png deleted file mode 100644 index c4e39457b..000000000 Binary files a/_preview/419/_images/fbfaaaebf1974140e69d13218d4a8a939db0f64462fc826d3ce36c144c84077c.png and /dev/null differ diff --git a/_preview/419/_images/fe028b4765ff40d9fe068f2662802ad65b6aa0fc1d72cc21dc786cedd2d6dc8e.png b/_preview/419/_images/fe028b4765ff40d9fe068f2662802ad65b6aa0fc1d72cc21dc786cedd2d6dc8e.png deleted file mode 100644 index f077c69fc..000000000 Binary files a/_preview/419/_images/fe028b4765ff40d9fe068f2662802ad65b6aa0fc1d72cc21dc786cedd2d6dc8e.png and /dev/null differ diff --git a/_preview/419/_images/foundations_diagram.png b/_preview/419/_images/foundations_diagram.png deleted file mode 100644 index d0aae8bcf..000000000 Binary files a/_preview/419/_images/foundations_diagram.png and /dev/null differ diff --git a/_preview/419/_images/github-clone-fork.png b/_preview/419/_images/github-clone-fork.png deleted file mode 100644 index b2cca525c..000000000 Binary files a/_preview/419/_images/github-clone-fork.png and /dev/null differ diff --git a/_preview/419/_images/github-repos.png b/_preview/419/_images/github-repos.png deleted file mode 100644 index 127a2f658..000000000 Binary files a/_preview/419/_images/github-repos.png and /dev/null differ diff --git a/_preview/419/_images/gitworkflow.gif b/_preview/419/_images/gitworkflow.gif deleted file mode 100644 index af453f033..000000000 Binary files a/_preview/419/_images/gitworkflow.gif and /dev/null differ diff --git a/_preview/419/_images/hsv2gray.png b/_preview/419/_images/hsv2gray.png deleted file mode 100644 index 12287ac17..000000000 Binary files a/_preview/419/_images/hsv2gray.png and /dev/null differ diff --git a/_preview/419/_images/interface_labeled.png b/_preview/419/_images/interface_labeled.png deleted file mode 100644 index 59deea746..000000000 Binary files a/_preview/419/_images/interface_labeled.png and /dev/null differ diff --git a/_preview/419/_images/jupyter_gui.png b/_preview/419/_images/jupyter_gui.png deleted file mode 100644 index 8ac5dfe39..000000000 Binary files a/_preview/419/_images/jupyter_gui.png and /dev/null differ diff --git a/_preview/419/_images/local-execution-model.gif b/_preview/419/_images/local-execution-model.gif deleted file mode 100644 index 795c417e9..000000000 Binary files a/_preview/419/_images/local-execution-model.gif and /dev/null differ diff --git a/_preview/419/_images/m.png b/_preview/419/_images/m.png deleted file mode 100644 index bd636a62d..000000000 Binary files a/_preview/419/_images/m.png and /dev/null differ diff --git a/_preview/419/_images/magics.png b/_preview/419/_images/magics.png deleted file mode 100644 index 65b74c5e9..000000000 Binary files a/_preview/419/_images/magics.png and /dev/null differ diff --git a/_preview/419/_images/markdown.png b/_preview/419/_images/markdown.png deleted file mode 100644 index 12ad4b4a6..000000000 Binary files a/_preview/419/_images/markdown.png and /dev/null differ diff --git a/_preview/419/_images/markdown_eq.png b/_preview/419/_images/markdown_eq.png deleted file mode 100644 index 9314fb2b3..000000000 Binary files a/_preview/419/_images/markdown_eq.png and /dev/null differ diff --git a/_preview/419/_images/markdown_eq_inline.png b/_preview/419/_images/markdown_eq_inline.png deleted file mode 100644 index 810e931b7..000000000 Binary files a/_preview/419/_images/markdown_eq_inline.png and /dev/null differ diff --git a/_preview/419/_images/misc.png b/_preview/419/_images/misc.png deleted file mode 100644 index 33814e727..000000000 Binary files a/_preview/419/_images/misc.png and /dev/null differ diff --git a/_preview/419/_images/mysci.png b/_preview/419/_images/mysci.png deleted file mode 100644 index ffd37a733..000000000 Binary files a/_preview/419/_images/mysci.png and /dev/null differ diff --git a/_preview/419/_images/notebook-interface_labeled.png b/_preview/419/_images/notebook-interface_labeled.png deleted file mode 100644 index 0525c649d..000000000 Binary files a/_preview/419/_images/notebook-interface_labeled.png and /dev/null differ diff --git a/_preview/419/_images/perceptually-sequential.png b/_preview/419/_images/perceptually-sequential.png deleted file mode 100644 index fadd85c28..000000000 Binary files a/_preview/419/_images/perceptually-sequential.png and /dev/null differ diff --git a/_preview/419/_images/pretty-earth.png b/_preview/419/_images/pretty-earth.png deleted file mode 100644 index 96de175fa..000000000 Binary files a/_preview/419/_images/pretty-earth.png and /dev/null differ diff --git a/_preview/419/_images/ps.png b/_preview/419/_images/ps.png deleted file mode 100644 index 381a1a976..000000000 Binary files a/_preview/419/_images/ps.png and /dev/null differ diff --git a/_preview/419/_images/pulling.gif b/_preview/419/_images/pulling.gif deleted file mode 100644 index 20389de00..000000000 Binary files a/_preview/419/_images/pulling.gif and /dev/null differ diff --git a/_preview/419/_images/pullrequest.gif b/_preview/419/_images/pullrequest.gif deleted file mode 100644 index c8cb2cebb..000000000 Binary files a/_preview/419/_images/pullrequest.gif and /dev/null differ diff --git a/_preview/419/_images/pushing.gif b/_preview/419/_images/pushing.gif deleted file mode 100644 index a09edf5e2..000000000 Binary files a/_preview/419/_images/pushing.gif and /dev/null differ diff --git a/_preview/419/_images/qualitative.png b/_preview/419/_images/qualitative.png deleted file mode 100644 index 2e0c25793..000000000 Binary files a/_preview/419/_images/qualitative.png and /dev/null differ diff --git a/_preview/419/_images/raw.png b/_preview/419/_images/raw.png deleted file mode 100644 index 9d50a34a8..000000000 Binary files a/_preview/419/_images/raw.png and /dev/null differ diff --git a/_preview/419/_images/remote-execution-model.gif b/_preview/419/_images/remote-execution-model.gif deleted file mode 100644 index f9cdb7119..000000000 Binary files a/_preview/419/_images/remote-execution-model.gif and /dev/null differ diff --git a/_preview/419/_images/review-approve.png b/_preview/419/_images/review-approve.png deleted file mode 100644 index d48e19c9a..000000000 Binary files a/_preview/419/_images/review-approve.png and /dev/null differ diff --git a/_preview/419/_images/review-fileschanged.png b/_preview/419/_images/review-fileschanged.png deleted file mode 100644 index b50c326bd..000000000 Binary files a/_preview/419/_images/review-fileschanged.png and /dev/null differ diff --git a/_preview/419/_images/review-inline.png b/_preview/419/_images/review-inline.png deleted file mode 100644 index 076181e1b..000000000 Binary files a/_preview/419/_images/review-inline.png and /dev/null differ diff --git a/_preview/419/_images/review-request.png b/_preview/419/_images/review-request.png deleted file mode 100644 index 72f035b02..000000000 Binary files a/_preview/419/_images/review-request.png and /dev/null differ diff --git a/_preview/419/_images/running-tabs-kernels.png b/_preview/419/_images/running-tabs-kernels.png deleted file mode 100644 index f3c63f93d..000000000 Binary files a/_preview/419/_images/running-tabs-kernels.png and /dev/null differ diff --git a/_preview/419/_images/s1.png b/_preview/419/_images/s1.png deleted file mode 100644 index 738f6a059..000000000 Binary files a/_preview/419/_images/s1.png and /dev/null differ diff --git a/_preview/419/_images/s2.png b/_preview/419/_images/s2.png deleted file mode 100644 index c47fc1361..000000000 Binary files a/_preview/419/_images/s2.png and /dev/null differ diff --git a/_preview/419/_images/sequential.png b/_preview/419/_images/sequential.png deleted file mode 100644 index 5730a3e82..000000000 Binary files a/_preview/419/_images/sequential.png and /dev/null differ diff --git a/_preview/419/_images/sequential2.png b/_preview/419/_images/sequential2.png deleted file mode 100644 index 162545b58..000000000 Binary files a/_preview/419/_images/sequential2.png and /dev/null differ diff --git a/_preview/419/_images/special_vars.png b/_preview/419/_images/special_vars.png deleted file mode 100644 index 72812f4dc..000000000 Binary files a/_preview/419/_images/special_vars.png and /dev/null differ diff --git a/_preview/419/_images/suggestion.png b/_preview/419/_images/suggestion.png deleted file mode 100644 index fe15a3f03..000000000 Binary files a/_preview/419/_images/suggestion.png and /dev/null differ diff --git a/_preview/419/_images/table-contents.png b/_preview/419/_images/table-contents.png deleted file mode 100644 index 24dbd675b..000000000 Binary files a/_preview/419/_images/table-contents.png and /dev/null differ diff --git a/_preview/419/_images/terminal.png b/_preview/419/_images/terminal.png deleted file mode 100644 index 30693eae2..000000000 Binary files a/_preview/419/_images/terminal.png and /dev/null differ diff --git a/_preview/419/_images/txt-editor.png b/_preview/419/_images/txt-editor.png deleted file mode 100644 index 07ff3d668..000000000 Binary files a/_preview/419/_images/txt-editor.png and /dev/null differ diff --git a/_preview/419/_images/xarray-split-apply-combine.jpeg b/_preview/419/_images/xarray-split-apply-combine.jpeg deleted file mode 100644 index 25d4b84de..000000000 Binary files a/_preview/419/_images/xarray-split-apply-combine.jpeg and /dev/null differ diff --git a/_preview/419/_sources/appendix/how-to-contribute.md b/_preview/419/_sources/appendix/how-to-contribute.md deleted file mode 100644 index f001d1e8b..000000000 --- a/_preview/419/_sources/appendix/how-to-contribute.md +++ /dev/null @@ -1,70 +0,0 @@ -# Pythia Foundations Contributor's Guide - -```{note} -This content is under construction! -``` - -General information on how to contribute to any Project Pythia repository -may be found [here][pythia contributor's guide]. - -This page will eventually contain a full guide to contributing to Project Pythia. As GitHub Pull Requests are an important part of contributing to Pythia, this guide will cross-reference tutorials on GitHub and Pull Requests. - -If you need to comment on anything in Pythia Foundations you feel needs work, you can use the "open issue" or "suggest edit" buttons at the top of any Pythia Foundations page. These buttons appear when you hover over the GitHub Octocat logo. Clicking on these buttons will take you to the relevant page on GitHub, where the entirety of the Pythia Foundations material is hosted. In order to actually suggest changes, you must have a free GitHub account, as listed in the GitHub section of Pythia Foundations. This contributor's guide is strictly for Pythia Foundations; for general Project Pythia contribution guidelines, see the main [Project Pythia Contributor's Guide][pythia contributor's guide]. - -To quickly provide feedback about minor issues without the use of GitHub, you can also use this [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSeVa1TC9xM-dk7qIE2e8bsgSrIP82yYDNw3wew3J46eREJa4w/viewform?usp=sf_link). - -## Contributing a new Jupyter Notebook - -If you'd like to contribute a Jupyter Notebook to these materials, please reference our [template](template) viewable on the next page. This template is available to you in `appendix/template.ipynb` if you've cloned the [source repository](https://github.com/ProjectPythia/pythia-foundations), or available as a download [directly from GitHub](https://github.com/ProjectPythia/pythia-foundations/raw/main/appendix/template.ipynb). - -## Building the site - -### Create a conda environment - -The first time you check out this repository, run: - -```bash -conda env update -f environment.yml -``` - -This will create or update the dev environment (`pythia-book-dev`). - -### Install `pre-commit` hooks - -This repository includes `pre-commit` hooks (defined in `.pre-commit-config.yaml`). To activate/install these pre-commit hooks, run: - -```bash -conda activate pythia-book-dev -pre-commit install -``` - -This is also a one-time step. - -_NOTE_: The `pre-commit` package is already installed via the `pythia-book-dev` conda environment. - -### Building the book locally - -To build the book locally, run the following: - -```bash -conda activate pythia-book-dev -jupyter-book build . -``` - -Finally, you can view the book by opening the file `_build/html/index.html` with your favorite web browser. On most platforms you can simply run: - -```bash -open _build/html/index.html -``` - -### Keeping your dev environment up to date - -It's good practice to update the packages in your `pythia-book-dev` conda environment frequently to their latest versions, especially if it's been a while since you used it. If the `jupyter-book build .` command above generates error messages, that is a good indication that your conda environment may be out of date. - -To update all packages in the currently activated environment to their latest versions, do this: - -```bash -conda update --all -``` - -[pythia contributor's guide]: https://projectpythia.org/contributing.html diff --git a/_preview/419/_sources/appendix/template.ipynb b/_preview/419/_sources/appendix/template.ipynb deleted file mode 100644 index 4b7942ba1..000000000 --- a/_preview/419/_sources/appendix/template.ipynb +++ /dev/null @@ -1,367 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Project Pythia Notebook Template\n", - "\n", - "## How to Use This Page\n", - "\n", - "This page is designed as a template. As such, each section contains instructions for the content added to the equivalent section of a new notebook, with the exception of this section, and the Setting Up a New Notebook section. Because this is not a tutorial, the overall structure of the page does not need to be cohesive.\n", - "\n", - "## Setting Up a New Notebook\n", - "\n", - "This section lists the first steps for configuring a Jupyter Notebook for inclusion in Pythia Foundations. First, if you have an image relevant to your notebook, such as a [logo](https://github.com/numpy/numpy/blob/main/doc/source/_static/numpylogo.svg), link to this image at the top of the notebook. The following Markdown example illustrates the correct technique for linking such an image:\n", - "\n", - "> `![](http://link.com/to/image.png \"image alt text\")`\n", - "\n", - "You can also use an `img` tag in raw HTML to embed your logo or other image. Second, make sure to add an HTML `alt` tag to any image in your notebook. This includes any type of image, including logos, wherever and however they appear in your notebook. Adding this tag improves accessibility and allows more people to properly access your notebook.\n", - "\n", - "\"Project" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Project Pythia Notebook Template\n", - "\n", - "Each notebook must be properly titled with a top level Markdown header, i.e., a header title prefixed by a single # mark. Nowhere else in the notebook should you use a top level header. This header will be automatically used by the Pythia book-building process to generate the page title, which will then be added to the navbar, table of contents, etc. As such, the header needs to be short, concise, and descriptive. After the header line, add a separate Jupyter Notebook cell with the text `---`. This adds a separating line used to separate the title from the overview and prerequisites. This technique will also be used later to separate other sections." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "If your notebook contains an introductory paragraph, include the paragraph at the start of this section. Such paragraphs must be short, and relevant to the content of the notebook. After the introductory paragraph, it is required to list the notebook topics, in the format shown below:\n", - "\n", - "1. This is a numbered list of the specific topics\n", - "1. These should map approximately to your main sections of content\n", - "1. Or each second-level, `##`, header in your notebook\n", - "1. Keep the size and scope of your notebook in check\n", - "1. And be sure to let the reader know up front the important concepts they'll be leaving with" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "This part of the Pythia Notebook Template was inspired by another template; in this case, [the template](https://github.com/alan-turing-institute/the-turing-way/blob/master/book/templates/chapter-template/chapter-landing-page.md) for the Jupyter Book known as [The Turing Way](https://the-turing-way.netlify.app).\n", - "\n", - "Following the overview section, the prerequisites section must enumerate a list of concepts and Python packages. These concepts and packages must comprise the knowledge that readers of your notebook **must know and understand** in order to successfully learn the notebook material. Each concept or package listed must link to a Pythia Foundations tutorial, or to a relevant external resource. To build the prerequisite table, first copy the following Markdown table into your notebook. You must then edit the table to contain your notebook prerequisites. Each row must contain the name of the concept, along with a link to the tutorial, either on Pythia Foundations or a relevant external resource. It must also be noted whether the concept is helpful or necessary.\n", - "\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Intro to Cartopy](../core/cartopy/cartopy) | Necessary | |\n", - "| [Understanding of NetCDF](some-link-to-external-resource) | Helpful | Familiarity with metadata structure |\n", - "| Project management | Helpful | |\n", - "\n", - "- **Time to learn:** You must provide an estimate of the total time to learn the listed concepts. The general rule is to estimate 5 minutes for each subsection in each concept, or 10 minutes for especially lengthy subsections. Add the estimates for each subsection to obtain the time to learn. Also, please note that overestimates are better than underestimates.\n", - "- **System requirements**: \n", - " - If there are any system, version, or non-Python software requirements for the material in your notebook, these must be listed in a system requirement list. \n", - " - If your notebook has no extra requirements, the **System Requirements** section should be completely removed. \n", - " - Note that Python packages do not count as system requirements; these should be listed in the Imports section." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "Before beginning this section, add a Markdown cell with a `---` divider. This section should list import statements for any Python packages required for your notebook content. Optionally, you can include a description above the code cell as well." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import sys" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Your first content section" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Replace this template section with your first section of tutorial material; all tutorial material should roughly match up with the objectives stated in the Overview section. Your notebook sections should be laid out as a narrative, each containing interspersed Markdown text, images, code cells, and other content as necessary." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Code cells like this are an essential part of your notebook\n", - "print(\"Hello world!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A content subsection\n", - "To provide more detail about concepts in content sections, it is recommended to create content subsections. As shown in this template section, subsections are added through lower-level Markdown headers, and automatically populate navbars, both when viewing the notebook in JupyterLab and when viewing the notebook as a Pythia Foundations tutorial page." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# some subsection code\n", - "new = \"helpful information\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Another content subsection\n", - "This subsection was created in the same way as the previous subsection. Subsections often contain detailed information relevant to the material. An example relevant to this template is \"Try to avoid using code comments as narrative; instead, let them only exist for brief clarification as needed.\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Your second content section\n", - "The second content section should roughly match up with the second learning objective of your notebook. For this template, the objective in question is to learn levels of Markdown headers. Below is a demonstration of Markdown header levels; however, be aware that each new header is incorporated into the navbars." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### This example is\n", - "\n", - "#### a quick demonstration\n", - "\n", - "##### of further and further\n", - "\n", - "###### header levels" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each section in your notebook can also contain $\\LaTeX$ equations, enabled through MathJax. In the following example, we illustrate some sample MathJax equations. (Rendering instructions, as well as detailed information about MathJax, can be found in [this documentation](https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Typesetting%20Equations.html).)\n", - "\n", - "\\begin{align}\n", - "\\dot{x} & = \\sigma(y-x) \\\\\n", - "\\dot{y} & = \\rho x - y - xz \\\\\n", - "\\dot{z} & = -\\beta z + xy\n", - "\\end{align}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are many helpful resources for learning Markdown and customizing Jupyter Markdown cells listed on [this useful guide](https://www.markdownguide.org/basic-syntax/). In addition, there is information on formatting relevant specifically to Jupyter on this [Jupyter documentation page](https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Working%20With%20Markdown%20Cells.html). Finally, perfectionism is encouraged in Pythia Foundations, and there are many available resources for formatting notebooks in a perfectionistic manner." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Last Section\n", - "\n", - "It is possible to embed raw HTML into Jupyter Markdown cells, as shown above with the Project Pythia logo. This allows for many forms of additional content; the most used form in Pythia is message boxes, as illustrated below. (If you are viewing this page as a Jupyter Notebook, you can also edit the following Markdown cell to view the underlying code.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " This is an info box. Info boxes contain additional information about tutorial concepts.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Making a notebook for Pythia inevitably requires some trial and error for formatting, among other things. If you feel the formatting is lacking in some way, feel free to adjust it in different ways until it is up to your standards. Copying and editing Markdown cells is a good way to try different formatting options.\n", - "\n", - "In addition, there are other types of boxes, known as `admonitions`, that can be inserted into a tutorial page:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Success

\n", - " This is a success box. Success boxes are usually placed at the end of a set of examples, and usually show a message relating to the final state of the examples.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Warning

\n", - " This is a warning box. Warning boxes are usually used to indicate a situation where making a mistake, such as a typo, can cause issues with the tutorial content.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Danger

\n", - " This is a danger box. Danger boxes are usually used to indicate a situation where making a mistake, such as a typo, can cause more serious issues such as loss of data.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In addition, it is helpful and highly recommended to add cell tags to your Jupyter cells. These tags allow for [customization](https://jupyterbook.org/interactive/hiding.html) of content display, especially for code cells. In addition, cell tags provide a means for [demonstrating errors](https://jupyterbook.org/content/execute.html#dealing-with-code-that-raises-errors) without breaking any production environments. If you are unfamiliar with cell tags, you can review this [brief demonstration](https://jupyterbook.org/content/metadata.html#jupyter-cell-tags) provided by Jupyter Book; this demonstration covers cell tags in Jupyter Notebook and Jupyter Lab, as well as fully manual cell tags." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "Before adding a summary, you must first add another Markdown cell containing `---`, which marks the end of the content body. A good Summary section contains a brief single paragraph that summarizes the tutorial content. The key content elements and their relation to the tutorial objectives should also be covered. Finally, the most important concepts should be listed again in detail.\n", - "\n", - "### What's next?\n", - "This section should briefly describe the content in the page following your tutorial sequentially. You can find the page sequentially following yours using the Next link at the bottom of the page, or using the sidebar; Jupyter Book should pre-populate this. In addition, if your tutorial leads into other Pythia Foundations content, or tutorials found outside Pythia Foundations, these other tutorials can be linked to as well." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and references\n", - "In this section, you must provide detailed citations and references to any external content used in your tutorial. Many types of external content are designed in as much detail as Pythia Foundations pages, and crediting the author is essential. In addition, this section can contain links to additional external content, such as reading, documentation, etc. Once this section is complete, your notebook is finished. After giving your new notebook a quick review, you can request the addition of the notebook to Pythia Foundations by sending the team a GitHub Pull Request. Here are a few final notes pertaining to working with Jupyter and Pythia:\n", - " - In order to confirm that your notebook runs from start to finish without errors, hangs, etc., go to the `Kernel` menu in Jupyter Lab and select `Restart Kernel and Run All Cells`.\n", - " - In order to prepare your notebook to be committed to Pythia Foundations, go to the `Kernel` menu in Jupyter Lab and select `Restart Kernel and Clear All Outputs`. After the notebook is committed, the Jupyter cells will be run and optimized for Pythia automatically.\n", - " - If you wish to take credit for your notebook, you can add contact information in this section; this is completely optional.\n", - " - It is very important that any code, information, images, etc. referenced in the above sections of your notebook contains appropriate attribution of authorship in this section.\n", - " - Finally, it is imperative that you must have a legal right to use any content included in your notebook. **Do not commit copyright infringement or plagiarism.**\n", - " \n", - "The Project Pythia team thanks you greatly for contributing to Pythia Foundations." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - }, - "nbdime-conflicts": { - "local_diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 0, - "op": "addrange", - "valuelist": [ - "Python 3" - ] - }, - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "display_name", - "op": "patch" - } - ], - "key": "kernelspec", - "op": "patch" - } - ], - "remote_diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 0, - "op": "addrange", - "valuelist": [ - "Python3" - ] - }, - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "display_name", - "op": "patch" - } - ], - "key": "kernelspec", - "op": "patch" - } - ] - }, - "toc-autonumbering": false - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/cartopy.md b/_preview/419/_sources/core/cartopy.md deleted file mode 100644 index 03a47555a..000000000 --- a/_preview/419/_sources/core/cartopy.md +++ /dev/null @@ -1,20 +0,0 @@ -# Cartopy - -This section contains tutorials on plotting maps with [Cartopy](https://scitools.org.uk/cartopy/docs/latest/); it is cross-referenced with tutorials on [Xarray](xarray) and [Matplotlib](matplotlib). - ---- - -From the [Cartopy website](https://scitools.org.uk/cartopy/docs/latest): - -> Cartopy is a Python package designed for geospatial data processing in order to -> produce maps and other geospatial data analyses. -> -> Cartopy makes use of the powerful PROJ.4, NumPy and Shapely libraries and includes a programmatic interface -> built on top of Matplotlib for the creation of publication quality maps. -> -> Key features of Cartopy are its object-oriented [projection definitions](https://scitools.org.uk/cartopy/docs/latest/reference/crs.html#list-of-projections), -> and its ability to transform points, lines, vectors, polygons and images between those projections. - -Before working through the Cartopy notebooks in this section of Pythia Foundations, you should first have a basic knowledge of [Matplotlib](matplotlib). - -In addition, please note that the geographic-features library used by Cartopy makes use of shapefiles directly served by [Natural Earth](https://www.naturalearthdata.com/). diff --git a/_preview/419/_sources/core/cartopy/cartopy.ipynb b/_preview/419/_sources/core/cartopy/cartopy.ipynb deleted file mode 100644 index 79f686b41..000000000 --- a/_preview/419/_sources/core/cartopy/cartopy.ipynb +++ /dev/null @@ -1,757 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "# Introduction to Cartopy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "___" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "\n", - "The concepts covered in this section include:\n", - "\n", - "1. Learning core Cartopy concepts: map projections and `GeoAxes`\n", - "2. Exploring some of Cartopy's map projections\n", - "3. Creating regional maps\n", - "\n", - "This tutorial will lead you through some basics of creating maps with specified projections using Cartopy, and adding geographical features (like coastlines and borders) to those maps.\n", - "\n", - "Plotting data on map projections will be covered in later tutorials." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Matplotlib](../matplotlib) | Necessary | |\n", - "\n", - "- **Time to learn**: 30 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "___" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "\n", - "Here, we import the main libraries of Cartopy: crs and feature. In addition, we import numpy, as well as matplotlib's pyplot interface. Finally, we import a library called warnings, and use it to remove extraneous warnings that Cartopy produces in later examples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import warnings\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from cartopy import crs as ccrs, feature as cfeature\n", - "\n", - "# Suppress warnings issued by Cartopy when downloading data files\n", - "warnings.filterwarnings('ignore')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "___" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Basic concepts: map projections and `GeoAxes`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Extend Matplotlib's `axes` into georeferenced `GeoAxes`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Recall from earlier tutorials that a *figure* in Matplotlib has two elements: a `Figure` object, and a list of one or more `Axes` objects (subplots).\n", - "\n", - "Since we imported `cartopy.crs`, we now have access to Cartopy's *Coordinate Reference System*, which contains many geographical projections. We can specify one of these projections for an `Axes` object to convert it into a `GeoAxes` object. This will effectively *georeference* the subplot. Examples of converting `Axes` objects into `GeoAxes` objects can be found later in this section." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a map with a specified projection\n", - "\n", - "In this example, we'll create a `GeoAxes` object that uses the `PlateCarree` projection. `PlateCarree` is a global lat-lon map projection in which each point is evenly spaced in terms of degrees. The name \"Plate Carree\" is French for \"flat square\"." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(11, 8.5))\n", - "ax = plt.subplot(1, 1, 1, projection=ccrs.PlateCarree(central_longitude=-75))\n", - "ax.set_title(\"A Geo-referenced subplot, Plate Carree projection\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Although the figure seems empty, it has, in fact, been georeferenced using a map projection; this projection is provided by Cartopy's `crs` (coordinate reference system) class. We can now add in cartographic features, in the form of *shapefiles*, to our subplot. One such cartographic feature is coastlines, which can be added to our subplot using the callable `GeoAxes` method simply called `coastlines`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ax.coastlines()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " To get the figure to display again with the features that we've added since the original display, just type the name of the Figure object in its own cell.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add cartographic features to the map" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Cartopy provides other cartographic features via its `features` class, which was imported at the beginning of this page, under the name `cfeature`. These cartographic features are laid out as data in shapefiles. The shapefiles are downloaded when their cartographic features are used for the first time in a script or notebook, and they are downloaded from https://www.naturalearthdata.com/. Once downloaded, they \"live\" in your `~/.local/share/cartopy` directory (note the `~` represents your home directory)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can add these features to our subplot via the `add_feature` method; this method allows the definition of attributes using arguments, similar to Matplotlib's `plot` method. A list of the various Natural Earth shapefiles can be found at https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html. In this example, we add borders and U. S. state lines to our subplot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='black')\n", - "ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once again, referencing the `Figure` object will re-render the figure in the notebook, now including the two features." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Explore some of Cartopy's map projections" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " You can find a list of supported projections in Cartopy, with examples, at https://scitools.org.uk/cartopy/docs/latest/reference/crs.html\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Mollweide Projection (often used with global satellite mosaics)\n", - "\n", - "To save typing later, we can define a projection object to store the definition of the map projection. We can then use this object in the `projection` kwarg of the `subplot` method when creating a `GeoAxes` object. This allows us to use this exact projection in later scripts or Jupyter Notebook cells using simply the object name, instead of repeating the same call to `ccrs`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(11, 8.5))\n", - "projMoll = ccrs.Mollweide(central_longitude=0)\n", - "ax = plt.subplot(1, 1, 1, projection=projMoll)\n", - "ax.set_title(\"Mollweide Projection\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Add in the cartographic shapefiles\n", - "\n", - "This example shows how to add cartographic features to the Mollweide projection defined earlier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ax.coastlines()\n", - "ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue')\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Add a fancy background image to the map.\n", - "\n", - "We can also use the `stock_img` method to add a pre-created background to a Mollweide-projection plot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ax.stock_img()\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Lambert Azimuthal Equal Area Projection\n", - "\n", - "This example is similar to the above example set, except it uses a Lambert azimuthal equal-area projection instead:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(11, 8.5))\n", - "projLae = ccrs.LambertAzimuthalEqualArea(central_longitude=0.0, central_latitude=0.0)\n", - "ax = plt.subplot(1, 1, 1, projection=projLae)\n", - "ax.set_title(\"Lambert Azimuthal Equal Area Projection\")\n", - "ax.coastlines()\n", - "ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create regional maps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Cartopy's `set_extent` method\n", - "\n", - "For this example, let's create another PlateCarree projection, but this time, we'll use Cartopy's `set_extent` method to restrict the map coverage to a North American view. Let's also choose a lower resolution for coastlines, just to illustrate how one can specify that. In addition, let's also plot the latitude and longitude lines." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Natural Earth defines three resolutions for cartographic features, specified as the strings \"10m\", \"50m\", and \"110m\". Only one resolution can be used at a time, and the higher the number, the less detailed the feature becomes. You can view the documentation for this functionality at the following reference link: https://www.naturalearthdata.com/downloads/ " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "projPC = ccrs.PlateCarree()\n", - "lonW = -140\n", - "lonE = -40\n", - "latS = 15\n", - "latN = 65\n", - "cLat = (latN + latS) / 2\n", - "cLon = (lonW + lonE) / 2\n", - "res = '110m'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(11, 8.5))\n", - "ax = plt.subplot(1, 1, 1, projection=projPC)\n", - "ax.set_title('Plate Carree')\n", - "gl = ax.gridlines(\n", - " draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'\n", - ")\n", - "ax.set_extent([lonW, lonE, latS, latN], crs=projPC)\n", - "ax.coastlines(resolution=res, color='black')\n", - "ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')\n", - "ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Please note, even though the calls to the `subplot` method use different projections, the calls to `set_extent` use PlateCarree. This ensures that the values we passed into `set_extent` will be transformed from degrees into the values appropriate for the projection we use for the map.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The PlateCarree projection exaggerates the spatial extent of regions closer to the poles. In the following examples, we use `set_extent` with stereographic and Lambert-conformal projections, which display polar regions more accurately." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "projStr = ccrs.Stereographic(central_longitude=cLon, central_latitude=cLat)\n", - "fig = plt.figure(figsize=(11, 8.5))\n", - "ax = plt.subplot(1, 1, 1, projection=projStr)\n", - "ax.set_title('Stereographic')\n", - "gl = ax.gridlines(\n", - " draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'\n", - ")\n", - "ax.set_extent([lonW, lonE, latS, latN], crs=projPC)\n", - "ax.coastlines(resolution=res, color='black')\n", - "ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')\n", - "ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "projLcc = ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)\n", - "fig = plt.figure(figsize=(11, 8.5))\n", - "ax = plt.subplot(1, 1, 1, projection=projLcc)\n", - "ax.set_title('Lambert Conformal')\n", - "gl = ax.gridlines(\n", - " draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'\n", - ")\n", - "ax.set_extent([lonW, lonE, latS, latN], crs=projPC)\n", - "ax.coastlines(resolution='110m', color='black')\n", - "ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')\n", - "# End last line with a semicolon to suppress text output to the screen\n", - "ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Lat/lon labeling for projections other than Mercator and PlateCarree is a recent addition to Cartopy. As you can see, work still needs to be done to improve the placement of labels.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a regional map centered over New York State " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we set the domain, which defines the geographical region to be plotted. (This is used in the next section in a `set_extent` call.) Since these coordinates are expressed in degrees, they correspond to a PlateCarree projection, even though the map projection is set to LambertConformal." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Warning

\n", - " Be patient; when plotting a small geographical area, the high-resolution \"10m\" shapefiles are used by default. As a result, these plots take longer to create, especially if the shapefiles are not yet downloaded from Natural Earth. Similar issues can occur whenever a `GeoAxes` object is transformed from one coordinate system to another. (This will be covered in more detail in a subsequent page.)\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "latN = 45.2\n", - "latS = 40.2\n", - "lonW = -80.0\n", - "lonE = -71.5\n", - "cLat = (latN + latS) / 2\n", - "cLon = (lonW + lonE) / 2\n", - "projLccNY = ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add some predefined features\n", - "\n", - "Some cartographical features are predefined as constants in the `cartopy.feature` package. The resolution of these features depends on the amount of geographical area in your map, specified by `set_extent`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(15, 10))\n", - "ax = plt.subplot(1, 1, 1, projection=projLccNY)\n", - "ax.set_extent([lonW, lonE, latS, latN], crs=projPC)\n", - "ax.set_facecolor(cfeature.COLORS['water'])\n", - "ax.add_feature(cfeature.LAND)\n", - "ax.add_feature(cfeature.COASTLINE)\n", - "ax.add_feature(cfeature.BORDERS, linestyle='--')\n", - "ax.add_feature(cfeature.LAKES, alpha=0.5)\n", - "ax.add_feature(cfeature.STATES)\n", - "ax.add_feature(cfeature.RIVERS)\n", - "ax.set_title('New York and Vicinity');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Note:

\n", - " For high-resolution Natural Earth shapefiles such as this, while we could add Cartopy's OCEAN feature, it currently takes much longer to render on the plot. You can create your own version of this example, with the OCEAN feature added, to see for yourself how much more rendering time is added. Instead, we take the strategy of first setting the facecolor of the entire subplot to match that of water bodies in Cartopy. When we then layer on the LAND feature, pixels that are not part of the LAND shapefile remain in the water facecolor, which is the same color as the OCEAN.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Use lower-resolution shapefiles from Natural Earth\n", - "\n", - "In this example, we create a new map. This map uses lower-resolution shapefiles from Natural Earth, and also eliminates the plotting of country borders.\n", - "\n", - "This example requires much more code than previous examples on this page. First, we must create new objects associated with lower-resolution shapefiles. This is performed by the `NaturalEarthFeature` method, which is part of the Cartopy `feature` class. Second, we call `add_feature` to add the new objects to our new map." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(15, 10))\n", - "ax = plt.subplot(1, 1, 1, projection=projLccNY)\n", - "ax.set_extent((lonW, lonE, latS, latN), crs=projPC)\n", - "\n", - "# The features with names such as cfeature.LAND, cfeature.OCEAN, are higher-resolution (10m)\n", - "# shapefiles from the Naturalearth repository. Lower resolution shapefiles (50m, 110m) can be\n", - "# used by using the cfeature.NaturalEarthFeature method as illustrated below.\n", - "\n", - "resolution = '110m'\n", - "\n", - "land_mask = cfeature.NaturalEarthFeature(\n", - " 'physical',\n", - " 'land',\n", - " scale=resolution,\n", - " edgecolor='face',\n", - " facecolor=cfeature.COLORS['land'],\n", - ")\n", - "sea_mask = cfeature.NaturalEarthFeature(\n", - " 'physical',\n", - " 'ocean',\n", - " scale=resolution,\n", - " edgecolor='face',\n", - " facecolor=cfeature.COLORS['water'],\n", - ")\n", - "lake_mask = cfeature.NaturalEarthFeature(\n", - " 'physical',\n", - " 'lakes',\n", - " scale=resolution,\n", - " edgecolor='face',\n", - " facecolor=cfeature.COLORS['water'],\n", - ")\n", - "state_borders = cfeature.NaturalEarthFeature(\n", - " category='cultural',\n", - " name='admin_1_states_provinces_lakes',\n", - " scale=resolution,\n", - " facecolor='none',\n", - ")\n", - "\n", - "ax.add_feature(land_mask)\n", - "ax.add_feature(sea_mask)\n", - "ax.add_feature(lake_mask)\n", - "ax.add_feature(state_borders, linestyle='solid', edgecolor='black')\n", - "ax.set_title('New York and Vicinity; lower resolution');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A figure with two different regional maps\n", - "\n", - "Finally, let's create a figure with two subplots. On the first subplot, we'll repeat the high-resolution New York State map created earlier; on the second, we'll plot over a different part of the world." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create the figure object\n", - "fig = plt.figure(\n", - " figsize=(30, 24)\n", - ") # Notice we need a bigger \"canvas\" so these two maps will be of a decent size\n", - "\n", - "# First subplot\n", - "ax = plt.subplot(2, 1, 1, projection=projLccNY)\n", - "ax.set_extent([lonW, lonE, latS, latN], crs=projPC)\n", - "ax.set_facecolor(cfeature.COLORS['water'])\n", - "ax.add_feature(cfeature.LAND)\n", - "ax.add_feature(cfeature.COASTLINE)\n", - "ax.add_feature(cfeature.BORDERS, linestyle='--')\n", - "ax.add_feature(cfeature.LAKES, alpha=0.5)\n", - "ax.add_feature(cfeature.STATES)\n", - "ax.set_title('New York and Vicinity')\n", - "\n", - "# Set the domain for defining the second plot region.\n", - "latN = 70\n", - "latS = 30.2\n", - "lonW = -10\n", - "lonE = 50\n", - "cLat = (latN + latS) / 2\n", - "cLon = (lonW + lonE) / 2\n", - "\n", - "projLccEur = ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)\n", - "\n", - "# Second subplot\n", - "ax2 = plt.subplot(2, 1, 2, projection=projLccEur)\n", - "ax2.set_extent([lonW, lonE, latS, latN], crs=projPC)\n", - "ax2.set_facecolor(cfeature.COLORS['water'])\n", - "ax2.add_feature(cfeature.LAND)\n", - "ax2.add_feature(cfeature.COASTLINE)\n", - "ax2.add_feature(cfeature.BORDERS, linestyle='--')\n", - "ax2.add_feature(cfeature.LAKES, alpha=0.5)\n", - "ax2.add_feature(cfeature.STATES)\n", - "ax2.set_title('Europe');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## An example of plotting data\n", - "\n", - "First, we'll create a lat-lon grid and define some data on it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lon, lat = np.mgrid[-180:181, -90:91]\n", - "data = 2 * np.sin(3 * np.deg2rad(lon)) + 3 * np.cos(4 * np.deg2rad(lat))\n", - "plt.contourf(lon, lat, data)\n", - "plt.colorbar();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plotting data on a Cartesian grid is equivalent to plotting data in the PlateCarree projection, where meridians and parallels are all straight lines with constant spacing. As a result of this simplicity, the global datasets we use often begin in the PlateCarree projection.\n", - "\n", - "Once we create our map again, we can plot these data values as a contour map. We must also specify the `transform` keyword argument. This is an argument to a contour-plotting method that specifies the projection type currently used by our data. The projection type specified by this argument will be transformed into the projection type specified in the `subplot` method. Let's plot our data in the Mollweide projection to see how shapes change under a transformation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(11, 8.5))\n", - "ax = plt.subplot(1, 1, 1, projection=projMoll)\n", - "ax.coastlines()\n", - "dataplot = ax.contourf(lon, lat, data, transform=ccrs.PlateCarree())\n", - "plt.colorbar(dataplot, orientation='horizontal');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "___" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Cartopy allows for the georeferencing of Matplotlib `Axes` objects.\n", - "- Cartopy's `crs` class supports a variety of map projections.\n", - "- Cartopy's `feature` class allows for a variety of cartographic features to be overlaid on a georeferenced plot or subplot." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "___" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## What's Next?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the next notebook, we will delve further into how one can transform data that is defined in one coordinate reference system (`crs`) so it displays properly on a map that uses a different `crs`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and References\n", - "\n", - "1. [Cartopy Documentation](https://scitools.org.uk/cartopy/docs/latest/)\n", - "2. [Full list of projections in Cartopy](https://scitools.org.uk/cartopy/docs/latest/reference/crs.html) \n", - "3. [Maps with Cartopy (Ryan Abernathey)](https://rabernat.github.io/research_computing_2018/maps-with-cartopy.html)\n", - "4. [Map Projections (GeoCAT)](https://geocat-examples.readthedocs.io/en/latest/gallery/index.html#map-projections)\n", - "5. [NCAR xdev Cartopy Tutorial Video](https://www.youtube.com/watch?v=ivmd3RluMiw)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/data-formats.md b/_preview/419/_sources/core/data-formats.md deleted file mode 100644 index ea64ce755..000000000 --- a/_preview/419/_sources/core/data-formats.md +++ /dev/null @@ -1,7 +0,0 @@ -# Data Formats - -```{note} -This content is under construction! -``` - -There are many data file formats used commonly in the geosciences, such as NetCDF and GRIB. This section contains tutorials on how to interact with these files in Python. diff --git a/_preview/419/_sources/core/data-formats/netcdf-cf.ipynb b/_preview/419/_sources/core/data-formats/netcdf-cf.ipynb deleted file mode 100644 index 2abb46f88..000000000 --- a/_preview/419/_sources/core/data-formats/netcdf-cf.ipynb +++ /dev/null @@ -1,1021 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![NetCDF Logo](https://www.unidata.ucar.edu/images/logos/netcdf-400x400.png \"NetCDF Logo\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NetCDF and CF: The Basics\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "This tutorial will begin with an introduction to netCDF. The CF data model will then be covered, and finally, important implementation details for netCDF. The structure of the tutorial is as follows:\n", - "\n", - "1. Demonstrating gridded data\n", - "1. Demonstrating observational data" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Numpy Basics](../numpy/numpy-basics) | Necessary | |\n", - "| [Datetime](../datetime) | Necessary | |\n", - "\n", - "- **Time to learn**: 50 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "\n", - "Some of these imports will be familiar from previous tutorials. However, some of them likely look foreign; these will be covered in detail later in this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime, timedelta\n", - "\n", - "import numpy as np\n", - "from cftime import date2num\n", - "from netCDF4 import Dataset\n", - "from pyproj import Proj" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Gridded Data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's say we're working with some numerical weather forecast model output. First, we need to store the data in the netCDF format. Second, we need to ensure that the metadata follows the Climate and Forecasting conventions. These steps ensure that a dataset is available to as many scientific data tools as is possible. The examples in this section illustrate these steps in detail.\n", - "\n", - "To start, let's assume the following about our data:\n", - "* There are three spatial dimensions (`x`, `y`, and `press`) and one temporal dimension (`times`).\n", - "* The native coordinate system of the model is on a regular 3km x 3km grid (`x` and `y`) that represents the Earth on a Lambert conformal projection.\n", - "* The vertical dimension (`press`) consists of several discrete pressure levels in units of hPa.\n", - "* The time dimension consists of twelve consecutive hours (`times`), beginning at 2200 UTC on the current day.\n", - "\n", - "The following code generates the dimensional arrays just discussed:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "start = datetime.utcnow().replace(hour=22, minute=0, second=0, microsecond=0)\n", - "times = np.array([start + timedelta(hours=h) for h in range(13)])\n", - "\n", - "x = np.arange(-150, 153, 3)\n", - "y = np.arange(-100, 100, 3)\n", - "\n", - "press = np.array([1000, 925, 850, 700, 500, 300, 250])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In addition to dimensional arrays, we also need a variable of interest, which holds the data values at each unique dimensional index. In these examples, this variable is called `temps`, and holds temperature data. Note that the dimensions correspond to the ones we just created above." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps = np.random.randn(times.size, press.size, y.size, x.size)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating the file and dimensions\n", - "\n", - "The first step in setting up a new netCDF file is to create a new file in netCDF format and set up the shared dimensions we'll be using in the file. We'll be using the `netCDF4` library to do all of the requisite netCDF API calls." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nc = Dataset('forecast_model.nc', 'w', format='NETCDF4_CLASSIC', diskless=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - "

The netCDF file created in the above example resides in memory, not disk, due to the diskless=True argument. In order to create this file on disk, you must either remove this argument, or add the persist=True argument.

\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Danger

\n", - "

If you open an existing file with 'w' as the second argument, any data already in the file will be overwritten. If you would like to edit the file, or add to it, open it using 'a' as the second argument.

\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We start the setup of this new netCDF file by creating and adding global attribute metadata. These particular metadata elements are not required, but are recommended by the CF standard. In addition, adding these elements to the file is simple, and helps users keep track of the data. Therefore, it is helpful to add these metadata elements, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nc.Conventions = 'CF-1.7'\n", - "nc.title = 'Forecast model run'\n", - "nc.institution = 'Unidata'\n", - "nc.source = 'WRF-1.5'\n", - "nc.history = str(datetime.utcnow()) + ' Python'\n", - "nc.references = ''\n", - "nc.comment = ''" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This next example shows a plain-text representation of our netCDF file as it exists currently:\n", - "```\n", - "netcdf forecast_model {\n", - " attributes:\n", - " :Conventions = \"CF-1.7\" ;\n", - " :title = \"Forecast model run\" ;\n", - " :institution = \"Unidata\" ;\n", - " :source = \"WRF-1.5\" ;\n", - " :history = \"2019-07-16 02:21:52.005718 Python\" ;\n", - " :references = \"\" ;\n", - " :comment = \"\" ;\n", - "}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " This plain-text representation is known as netCDF Common Data Format Language, or CDL.\n", - "
\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Variables are an important part of every netCDF file; they are used to define data fields. However, before we can add any variables to our file, we must first define the dimensions of the data. In this example, we create dimensions called `x`, `y`, and `pressure`, and set the size of each dimension to the size of the corresponding data array. We then create an additional dimension, `forecast_time`, and set the size as None. This defines the dimension as \"unlimited\", meaning that if additional data values are added later, the netCDF file grows along this dimension." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nc.createDimension('forecast_time', None)\n", - "nc.createDimension('x', x.size)\n", - "nc.createDimension('y', y.size)\n", - "nc.createDimension('pressure', press.size)\n", - "nc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we view our file's CDL representation now, we can verify that the dimensions were successfully added to the netCDF file:\n", - "```\n", - "netcdf forecast_model {\n", - " dimensions:\n", - " forecast_time = UNLIMITED (currently 13) ;\n", - " x = 101 ;\n", - " y = 67 ;\n", - " pressure = 7 ;\n", - " attributes:\n", - " :Conventions = \"CF-1.7\" ;\n", - " :title = \"Forecast model run\" ;\n", - " :institution = \"Unidata\" ;\n", - " :source = \"WRF-1.5\" ;\n", - " :history = \"2019-07-16 02:21:52.005718 Python\" ;\n", - " :references = \"\" ;\n", - " :comment = \"\" ;\n", - "}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating and filling a variable" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Thus far, we have only added basic information to this netCDF dataset; namely, the dataset dimensions and some broad metadata. As described briefly above, variables are used to define data fields in netCDF files. Here, we create a `netCDF4 variable` to hold a data field; in this case, the forecast air temperature. In order to create this netCDF4 variable, we must specify the data type of the values in the data field. We also must specify which dimensions contained in the netCDF file are relevant to this data field. Finally, we can specify whether or not to compress the data using a form of `zlib`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_var = nc.createVariable(\n", - " 'Temperature',\n", - " datatype=np.float32,\n", - " dimensions=('forecast_time', 'pressure', 'y', 'x'),\n", - " zlib=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We have now created a netCDF4 variable, but it does not yet define a data field. In this example, we use Python to associate our temperature data with the new variable:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_var[:] = temps\n", - "temps_var" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also associate data with a variable sporadically. This example illustrates how to only associate one value per time step with the variable created earlier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "next_slice = 0\n", - "for temp_slice in temps:\n", - " temps_var[next_slice] = temp_slice\n", - " next_slice += 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point, this is the CDL representation of our dataset:\n", - "```\n", - "netcdf forecast_model {\n", - " dimensions:\n", - " forecast_time = UNLIMITED (currently 13) ;\n", - " x = 101 ;\n", - " y = 67 ;\n", - " pressure = 7 ;\n", - " variables:\n", - " float Temperature(forecast_time, pressure, y, x) ;\n", - " attributes:\n", - " :Conventions = \"CF-1.7\" ;\n", - " :title = \"Forecast model run\" ;\n", - " :institution = \"Unidata\" ;\n", - " :source = \"WRF-1.5\" ;\n", - " :history = \"2019-07-16 02:21:52.005718 Python\" ;\n", - " :references = \"\" ;\n", - " :comment = \"\" ;\n", - "}\n", - "```\n", - "We can also define metadata for this variable in the form of attributes; some specific attributes are required by the CF conventions. For example, the CF conventions require a `units` attribute to be set for all variables that represent a dimensional quantity. In addition, the value of this attribute must be parsable by the [UDUNITS](https://www.unidata.ucar.edu/software/udunits/) library. In this example, the temperatures are in Kelvin, so we set the units attribute to `'Kelvin'`. Next, we set the `long_name` and `standard_name` attributes, which are recommended for most datasets, but optional. The `long_name` attribute contains a longer and more detailed description of a variable. On the other hand, the `standard_name` attribute names a variable using descriptive words from a predefined word list contained in the CF conventions. Defining these attributes allows users of your datasets to understand what each variable in a dataset represents. Sometimes, data fields do not have valid data values at every dimension point. In this case, the standard is to use a filler value for these missing data values, and to set the `missing_value` attribute to this filler value. In this case, however, there are no missing values, so the `missing_value` attribute can be set to any unused value, or not set at all.\n", - "\n", - "There are many different sets of recommendations for attributes on netCDF variables. For example, here is NASA's set of recommended attributes:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> **NASA Dataset Interoperability Recommendations:**\n", - ">\n", - "> Section 2.2 - Include Basic CF Attributes\n", - ">\n", - "> Include where applicable: `units`, `long_name`, `standard_name`, `valid_min` / `valid_max`, `scale_factor` / `add_offset` and others." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_var.units = 'Kelvin'\n", - "temps_var.standard_name = 'air_temperature'\n", - "temps_var.long_name = 'Forecast air temperature'\n", - "temps_var.missing_value = -9999\n", - "temps_var" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is the variable section of our dataset's CDL, with the new attributes added:\n", - "```\n", - " variables:\n", - " float Temperature(forecast_time, pressure, y, x) ;\n", - " Temperature:units = \"Kelvin\" ;\n", - " Temperature:standard_name = \"air_temperature\" ;\n", - " Temperature:long_name = \"Forecast air temperature\" ;\n", - " Temperature:missing_value = -9999.0 ;\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coordinate variables" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Dimensions in a netCDF file only define size and alignment metadata. In order to properly orient data in time and space, it is necessary to create \"coordinate variables\", which define data values along each dimension. A coordinate variable is typically created as a one-dimensional variable, and has the same name as the corresponding dimension.\n", - "\n", - "To start, we define variables which define our `x` and `y` coordinate values. It is recommended to include certain attributes for each coordinate variable. First, you should include a `standard_name`, which allows for associating the variable with projections, among other things. (Projections will be covered in detail later in this page.) Second, you can include an `axis` attribute, which clearly defines the spatial or temporal direction referred to by the coordinate variable. This next example demonstrates how to set up these attributes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x_var = nc.createVariable('x', np.float32, ('x',))\n", - "x_var[:] = x\n", - "x_var.units = 'km'\n", - "x_var.axis = 'X' # Optional\n", - "x_var.standard_name = 'projection_x_coordinate'\n", - "x_var.long_name = 'x-coordinate in projected coordinate system'\n", - "\n", - "y_var = nc.createVariable('y', np.float32, ('y',))\n", - "y_var[:] = y\n", - "y_var.units = 'km'\n", - "y_var.axis = 'Y' # Optional\n", - "y_var.standard_name = 'projection_y_coordinate'\n", - "y_var.long_name = 'y-coordinate in projected coordinate system'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our dataset contains vertical data of air pressure as well, so we must define a coordinate variable for this axis; we can simply call this new variable `pressure`. Since this axis represents air pressure data, we can set a `standard_name` of `'air_pressure'`. With this `standard_name` attribute set, it should be obvious to users of this dataset that this variable represents a vertical axis, but for extra clarification, we also set the `axis` attribute as `'Z'`. We can also specify one more attribute, called `positive`. This attribute indicates whether the variable values increase or decrease as the dimension values increase. Setting this attribute is optional for some data; air pressure is one example. However, we still set the attribute here, for the sake of completeness." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "press_var = nc.createVariable('pressure', np.float32, ('pressure',))\n", - "press_var[:] = press\n", - "press_var.units = 'hPa'\n", - "press_var.axis = 'Z' # Optional\n", - "press_var.standard_name = 'air_pressure'\n", - "press_var.positive = 'down' # Optional" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Time coordinates must contain a `units` attribute; this attribute is a string value, and must have a form similar to the string`'seconds since 2019-01-06 12:00:00.00'`. 'seconds', 'minutes', 'hours', and 'days' are the most commonly used time intervals in these strings. It is not recommended to use 'months' or 'years' in time strings, as the length of these time intervals can vary.\n", - "\n", - "Before we can write data, we need to first convert our list of Python `datetime` objects to numeric values usable in time strings. We can perform this conversion by setting a time string in the format described above, then using the `date2num` method from the `cftime` library. An example of this is shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "time_units = f'hours since {times[0]:%Y-%m-%d 00:00}'\n", - "time_vals = date2num(times, time_units)\n", - "time_vals" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that the time string is set up, we have all of the necessary information to set up the attributes for a `forecast_time` coordinate variable. The creation of this variable is shown in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "time_var = nc.createVariable('forecast_time', np.int32, ('forecast_time',))\n", - "time_var[:] = time_vals\n", - "time_var.units = time_units\n", - "time_var.axis = 'T' # Optional\n", - "time_var.standard_name = 'time' # Optional\n", - "time_var.long_name = 'time'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This next example shows the CDL representation of the netCDF file's variables at this point. It is clear that much more information is now contained in this representation:\n", - "```\n", - " dimensions:\n", - " forecast_time = UNLIMITED (currently 13) ;\n", - " x = 101 ;\n", - " y = 67 ;\n", - " pressure = 7 ;\n", - " variables:\n", - " float x(x) ;\n", - " x:units = \"km\" ;\n", - " x:axis = \"X\" ;\n", - " x:standard_name = \"projection_x_coordinate\" ;\n", - " x:long_name = \"x-coordinate in projected coordinate system\" ;\n", - " float y(y) ;\n", - " y:units = \"km\" ;\n", - " y:axis = \"Y\" ;\n", - " y:standard_name = \"projection_y_coordinate\" ;\n", - " y:long_name = \"y-coordinate in projected coordinate system\" ;\n", - " float pressure(pressure) ;\n", - " pressure:units = \"hPa\" ;\n", - " pressure:axis = \"Z\" ;\n", - " pressure:standard_name = \"air_pressure\" ;\n", - " pressure:positive = \"down\" ;\n", - " float forecast_time(forecast_time) ;\n", - " forecast_time:units = \"hours since 2019-07-16 00:00\" ;\n", - " forecast_time:axis = \"T\" ;\n", - " forecast_time:standard_name = \"time\" ;\n", - " forecast_time:long_name = \"time\" ;\n", - " float Temperature(forecast_time, pressure, y, x) ;\n", - " Temperature:units = \"Kelvin\" ;\n", - " Temperature:standard_name = \"air_temperature\" ;\n", - " Temperature:long_name = \"Forecast air temperature\" ;\n", - " Temperature:missing_value = -9999.0 ;\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Auxiliary Coordinates" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our data are still not CF-compliant, because they do not contain latitude and longitude information, which is needed to properly locate the data. In order to add location data to a netCDF file, we must create so-called \"auxiliary coordinate variables\" for latitude and longitude. (In this case, the word \"auxiliary\" means that the variables are not simple one-dimensional variables.)\n", - "\n", - "In this next example, we use the `Proj` function, found in the `pyproj` library, to create projections of our coordinates. We can then use these projections to generate latitude and longitude values for our data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X, Y = np.meshgrid(x, y)\n", - "lcc = Proj({'proj': 'lcc', 'lon_0': -105, 'lat_0': 40, 'a': 6371000.0, 'lat_1': 25})\n", - "lon, lat = lcc(X * 1000, Y * 1000, inverse=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we have latitude and longitude values, we can create variables for those values. Both of these variables are two-dimensional; the dimensions in question are `y` and `x`. In order to convey that it contains the longitude information, we must set up the longitude variable with a `units` attribute of `'degrees_east'`. In addition, we can provide further clarity by setting a `standard_name` attribute of `'longitude'`. The case is the same for latitude, except the units are `'degrees_north'` and the `standard_name` is `'latitude'`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lon_var = nc.createVariable('lon', np.float64, ('y', 'x'))\n", - "lon_var[:] = lon\n", - "lon_var.units = 'degrees_east'\n", - "lon_var.standard_name = 'longitude' # Optional\n", - "lon_var.long_name = 'longitude coordinate'\n", - "\n", - "lat_var = nc.createVariable('lat', np.float64, ('y', 'x'))\n", - "lat_var[:] = lat\n", - "lat_var.units = 'degrees_north'\n", - "lat_var.standard_name = 'latitude' # Optional\n", - "lat_var.long_name = 'latitude coordinate'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that the auxiliary coordinate variables are created, we must identify them as coordinates for the `Temperature` variable. In order to identify the variables in this way, we set the `coordinates` attribute of the `Temperature` variable to a space-separated list of variables to identify, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_var.coordinates = 'lon lat'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The portion of the CDL showing the new latitude and longitude variables, as well as the updated `Temperature` variable, is listed below:\n", - "```\n", - " double lon(y, x);\n", - " lon:units = \"degrees_east\";\n", - " lon:long_name = \"longitude coordinate\";\n", - " lon:standard_name = \"longitude\";\n", - " double lat(y, x);\n", - " lat:units = \"degrees_north\";\n", - " lat:long_name = \"latitude coordinate\";\n", - " lat:standard_name = \"latitude\";\n", - " float Temperature(time, y, x);\n", - " Temperature:units = \"Kelvin\" ;\n", - " Temperature:standard_name = \"air_temperature\" ;\n", - " Temperature:long_name = \"Forecast air temperature\" ;\n", - " Temperature:missing_value = -9999.0 ;\n", - " Temperature:coordinates = \"lon lat\";\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coordinate System Information" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since the grid containing our data uses a Lambert conformal projection, adding this information to the dataset's metadata can clear up some possible confusion. We can most easily add this metadata information by making use of a \"grid mapping\" variable. A grid mapping variable is a \"placeholder\" variable containing all required grid-mapping information. Other variables that need to access this information can then reference this placeholder variable in their `grid_mapping` attribute.\n", - "\n", - "In this example, we create a grid-mapping variable; this new variable is then set up for a Lambert-conformal conic projection on a spherical globe. By setting this variable's `grid_mapping_name` attribute, we can indicate which CF-supported grid mapping this variable refers to. There are additional attributes that can also be set; however, the available options depend on the specific mapping." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "proj_var = nc.createVariable('lambert_projection', np.int32, ())\n", - "proj_var.grid_mapping_name = 'lambert_conformal_conic'\n", - "proj_var.standard_parallel = 25.0\n", - "proj_var.latitude_of_projection_origin = 40.0\n", - "proj_var.longitude_of_central_meridian = -105.0\n", - "proj_var.semi_major_axis = 6371000.0\n", - "proj_var" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we have created a grid-mapping variable, we can specify the grid mapping by setting the `grid_mapping attribute` to the variable name. In this example, we set the `grid_mapping` attribute on the `Temperature` variable:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_var.grid_mapping = 'lambert_projection' # or proj_var.name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is the portion of the CDL containing the modified `Temperature` variable, as well as the new grid-mapping `lambert_projection` variable:\n", - "```\n", - " variables:\n", - " int lambert_projection ;\n", - " lambert_projection:grid_mapping_name = \"lambert_conformal_conic ;\n", - " lambert_projection:standard_parallel = 25.0 ;\n", - " lambert_projection:latitude_of_projection_origin = 40.0 ;\n", - " lambert_projection:longitude_of_central_meridian = -105.0 ;\n", - " lambert_projection:semi_major_axis = 6371000.0 ;\n", - " float Temperature(forecast_time, pressure, y, x) ;\n", - " Temperature:units = \"Kelvin\" ;\n", - " Temperature:standard_name = \"air_temperature\" ;\n", - " Temperature:long_name = \"Forecast air temperature\" ;\n", - " Temperature:missing_value = -9999.0 ;\n", - " Temperature:coordinates = \"lon lat\" ;\n", - " Temperature:grid_mapping = \"lambert_projection\" ;\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Cell Bounds\n", - "\n", - "The use of \"bounds\" attributes is not required, but highly recommended. Here is a relevant excerpt from the NASA Dataset Interoperability Recommendations:\n", - "> **NASA Dataset Interoperability Recommendations:**\n", - ">\n", - "> Section 2.3 - Use CF \"bounds\" attributes\n", - ">\n", - "> CF conventions state: \"When gridded data does not represent the point values of a field but instead represents some characteristic of the field within cells of finite 'volume,' a complete description of the variable should include metadata that describes the domain or extent of each cell, and the characteristic of the field that the cell values represent.\"\n", - "\n", - "In this set of examples, consider a rain gauge which is read every three hours, but only dumped every six hours. The netCDF file for this gauge's data readings might look like this:\n", - " \n", - "```\n", - "netcdf precip_bucket_bounds {\n", - " dimensions:\n", - " lat = 12 ;\n", - " lon = 19 ;\n", - " time = 8 ;\n", - " tbv = 2;\n", - " variables:\n", - " float lat(lat) ;\n", - " float lon(lon) ;\n", - " float time(time) ;\n", - " time:units = \"hours since 2019-07-12 00:00:00.00\";\n", - " time:bounds = \"time_bounds\" ;\n", - " float time_bounds(time,tbv)\n", - " float precip(time, lat, lon) ;\n", - " precip:units = \"inches\" ;\n", - " data:\n", - " time = 3, 6, 9, 12, 15, 18, 21, 24;\n", - " time_bounds = 0, 3, 0, 6, 6, 9, 6, 12, 12, 15, 12, 18, 18, 21, 18, 24;\n", - "}\n", - "```\n", - "\n", - "Considering the coordinate variable for time, and the `bounds` attribute set for this variable, the below graph illustrates the times of the gauge's data readings:\n", - "```\n", - "|---X\n", - "|-------X\n", - " |---X\n", - " |-------X\n", - " |---X\n", - " |-------X\n", - " |---X\n", - " |-------X\n", - "0 3 6 9 12 15 18 21 24\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Observational Data\n", - "\n", - "Thus far, we have only worked with data arranged on grids. One common type of data, called \"in-situ\" or \"observational\" data, is usually arranged in other ways. The CF conventions for this type of data are called *Conventions for DSG (Discrete Sampling Geometries)*.\n", - "\n", - "For data that are regularly sampled (e.g., from a vertical profiler site), this is straightforward. For these examples, we will be using vertical profile data from three hypothetical profilers, located in Boulder, Norman, and Albany. These hypothetical profilers report data for every 10 m of altitude, from altitudes of 10 m up to (but not including) 1000 m. This first example illustrates how to set up latitude, longitude, altitude, and other necessary data for these profilers:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lons = np.array([-97.1, -105, -73.8])\n", - "lats = np.array([35.25, 40, 42.75])\n", - "heights = np.linspace(10, 1000, 10)\n", - "temps = np.random.randn(lats.size, heights.size)\n", - "stids = ['KBOU', 'KOUN', 'KALB']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creation and basic setup\n", - "First, we create a new netCDF file, and define dimensions for it, corresponding to altitude and latitude. Since we are working with observational profile data, we define these dimensions as `heights` and `station`. We then set the global `featureType` attribute to `'profile'`, which defines the file as holding profile data. In these examples, the term \"profile data\" is defined as \"an ordered set of data points along a vertical line at a fixed horizontal position and fixed time\". In addition, we define a placeholder dimension called str_len, which helps with storing station IDs as strings." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nc.close()\n", - "nc = Dataset('obs_data.nc', 'w', format='NETCDF4_CLASSIC', diskless=True)\n", - "nc.createDimension('station', lats.size)\n", - "nc.createDimension('heights', heights.size)\n", - "nc.createDimension('str_len', 4)\n", - "nc.Conventions = 'CF-1.7'\n", - "nc.featureType = 'profile'\n", - "nc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After this initial setup, the current state of our netCDF file is described in the following CDL:\n", - "```\n", - "netcdf obs_data {\n", - " dimensions:\n", - " station = 3 ;\n", - " heights = 10 ;\n", - " str_len = 4 ;\n", - " attributes:\n", - " :Conventions = \"CF-1.7\" ;\n", - " :featureType = \"profile\" ;\n", - "}\n", - "```\n", - "This example illustrates the setup of coordinate variables for latitude and longitude:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lon_var = nc.createVariable('lon', np.float64, ('station',))\n", - "lon_var.units = 'degrees_east'\n", - "lon_var.standard_name = 'longitude'\n", - "\n", - "lat_var = nc.createVariable('lat', np.float64, ('station',))\n", - "lat_var.units = 'degrees_north'\n", - "lat_var.standard_name = 'latitude'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When a coordinate variable refers to an instance of a feature, netCDF standards refer to it as an \"instance variable\". The latitude and longitude coordinate variables declared above are examples of instance variables. In this next example, we create an instance variable for altitude, referred to here as `heights`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "heights_var = nc.createVariable('heights', np.float32, ('heights',))\n", - "heights_var.units = 'meters'\n", - "heights_var.standard_name = 'altitude'\n", - "heights_var.positive = 'up'\n", - "heights_var[:] = heights" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Station IDs\n", - "Using the placeholder dimension defined earlier, we can write the station IDs of our profilers to a variable as well. The variable used to store these station IDs is two-dimensional; however, one of these dimensions only holds metadata designed to aid in converting strings to character arrays. We can also assign the attribute `cf_role` to this variable, with a value of `'profile_id'`. If certain software programs read this netCDF file, this attribute assists in identifying individual profiles." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "stid_var = nc.createVariable('stid', 'c', ('station', 'str_len'))\n", - "stid_var.cf_role = 'profile_id'\n", - "stid_var.long_name = 'Station identifier'\n", - "stid_var[:] = stids" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After adding station ID information, our file's updated CDL should resemble this example:\n", - "```\n", - "netcdf obs_data {\n", - " dimensions:\n", - " station = 3 ;\n", - " heights = 10 ;\n", - " str_len = 4 ;\n", - " variables:\n", - " double lon(station) ;\n", - " lon:units = \"degrees_east\" ;\n", - " lon:standard_name = \"longitude\" ;\n", - " double lat(station) ;\n", - " lat:units = \"degrees_north\" ;\n", - " lat:standard_name = \"latitude\" ;\n", - " float heights(heights) ;\n", - " heights:units = \"meters\" ;\n", - " heights:standard_name = \"altitude\";\n", - " heights:positive = \"up\" ;\n", - " char stid(station, str_len) ;\n", - " stid:cf_role = \"profile_id\" ;\n", - " stid:long_name = \"Station identifier\" ;\n", - " attributes:\n", - " :Conventions = \"CF-1.7\" ;\n", - " :featureType = \"profile\" ;\n", - "}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Writing the field\n", - "The final setup step for this netCDF file is to write our actual profile data to the file. In addition, we add an additional scalar variable, which holds the time of data capture for each profile:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "time_var = nc.createVariable('time', np.float32, ())\n", - "time_var.units = 'minutes since 2019-07-16 17:00'\n", - "time_var.standard_name = 'time'\n", - "time_var[:] = [5.0]\n", - "\n", - "temp_var = nc.createVariable('temperature', np.float32, ('station', 'heights'))\n", - "temp_var.units = 'celsius'\n", - "temp_var.standard_name = 'air_temperature'\n", - "temp_var.coordinates = 'lon lat heights time'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The auxiliary coordinate variables in this netCDF file are not proper coordinate variables, and are all associated with the `station` dimension. Therefore, the names of these variables must be listed in an attribute called `coordinates`. The final CDL of the variables, including the `coordinates` attribute, is shown below:\n", - "```\n", - " variables:\n", - " double lon(station) ;\n", - " lon:units = \"degrees_east\" ;\n", - " lon:standard_name = \"longitude\" ;\n", - " double lat(station) ;\n", - " lat:units = \"degrees_north\" ;\n", - " lat:standard_name = \"latitude\" ;\n", - " float heights(heights) ;\n", - " heights:units = \"meters\" ;\n", - " heights:standard_name = \"altitude\";\n", - " heights:positive = \"up\" ;\n", - " char stid(station, str_len) ;\n", - " stid:cf_role = \"profile_id\" ;\n", - " stid:long_name = \"Station identifier\" ;\n", - " float time ;\n", - " time:units = \"minutes since 2019-07-16 17:00\" ;\n", - " time:standard_name = \"time\" ;\n", - " float temperature(station, heights) ;\n", - " temperature:units = \"celsius\" ;\n", - " temperature:standard_name = \"air_temperature\" ;\n", - " temperature:coordinates = \"lon lat heights time\" ;\n", - "```\n", - "\n", - "These standards for storing DSG data in netCDF files can be used for profiler data, as shown in these examples, as well as timeseries and trajectory data, and any combination of these types of data models. You can also use these standards for datasets with differing amounts of data in each feature, using so-called \"ragged\" arrays. For more information on ragged arrays, or other elements of the CF DSG standards, see the [main documentation page](http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/cf-conventions.html#discrete-sampling-geometries), or try some of the [annotated DSG examples](http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/cf-conventions.html#appendix-examples-discrete-geometries)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "We have created examples of and discussed the structure of **netCDF** `Datasets`, both gridded and in-situ. In addition, we covered the Climate and Forecasting (**CF**) Conventions, and the setup of netCDF files that follow these conventions. netCDF `Datasets` are self-describing; in other words, their attributes, or *metadata*, are included. Other libraries in the Python scientific software ecosystem, such as `xarray` and `MetPy`, are therefore easily able to read in, write to, and analyze these `Datasets`.\n", - "\n", - "### What's Next?\n", - "In subsequent notebooks, we will work with netCDF `Datasets` built from actual, non-example data sources, both model and in-situ." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Resources and References\n", - "\n", - "- [CF Conventions doc (1.7)](http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/cf-conventions.html)\n", - "- [Jonathan Gregory's old CF presentation](http://cfconventions.org/Data/cf-documents/overview/viewgraphs.pdf)\n", - "- [NASA ESDS \"Dataset Interoperability Recommendations for Earth Science\"](https://earthdata.nasa.gov/user-resources/standards-and-references/dataset-interoperability-recommendations-for-earth-science)\n", - "- [CF Data Model (cfdm) python package tutorial](https://ncas-cms.github.io/cfdm/tutorial.html)\n", - "- [Tim Whiteaker's cfgeom python package (GitHub repo)](https://github.com/twhiteaker/CFGeom) and [(tutorial)]( https://twhiteaker.github.io/CFGeom/tutorial.html)\n", - "- [netCDF4 Documentation](https://unidata.github.io/netcdf4-python/)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/datetime.md b/_preview/419/_sources/core/datetime.md deleted file mode 100644 index 185df93ec..000000000 --- a/_preview/419/_sources/core/datetime.md +++ /dev/null @@ -1,14 +0,0 @@ -# Datetime - -```{note} -This content is under construction! -``` - -This section contains tutorials on dealing with times and calendars in scientific Python. The first and most basic of these tutorials covers the standard Python library known as [datetime](https://docs.python.org/3/library/datetime.html). - -When this chapter is fully built out, it will include a comprehensive guide to different time libraries, where to use them, and when they might be useful. This set of time libraries includes these libraries, among others: - -- [Numpy `datetime64`](https://numpy.org/doc/stable/reference/arrays.datetime.html) (for efficient vectorized date and time operations) -- [cftime library](https://unidata.github.io/cftime/) (for dealing with dates and times in non-standard calendars) - -These tutorials will be cross-referenced with other tutorials on time-related topics, such as dealing with timeseries data in [Pandas](pandas) and [Xarray](xarray). diff --git a/_preview/419/_sources/core/datetime/datetime.ipynb b/_preview/419/_sources/core/datetime/datetime.ipynb deleted file mode 100644 index b1e970523..000000000 --- a/_preview/419/_sources/core/datetime/datetime.ipynb +++ /dev/null @@ -1,562 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Times and Dates in Python" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "\n", - "Time is an essential component of nearly all geoscience data. Timescales commonly used in science can have many different orders of magnitude, from mere microseconds to millions or even billions of years. Some of these magnitudes are listed below:\n", - "\n", - "- microseconds for lightning\n", - "- hours for a supercell thunderstorm\n", - "- days for a global weather model\n", - "- millennia and beyond for the earth's climate\n", - "\n", - "To properly analyze geoscience data, you must have a firm understanding of how to handle time in Python. \n", - "\n", - "In this notebook, we will:\n", - "\n", - "1. Introduce the [time](https://docs.python.org/3/library/time.html) and [datetime](https://docs.python.org/3/library/datetime.html) modules from the Python Standard Library\n", - "1. Look at formatted input and output of dates and times\n", - "1. See how we can do simple arithmetic on date and time data, by making use of the `timedelta` object\n", - "1. Briefly make use of the [pytz](https://pypi.org/project/pytz/) module to handle some thorny time zone issues in Python." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Python Quickstart](../../foundations/quickstart) | Necessary | Understanding strings |\n", - "| Basic Python string formatting | Helpful | Try this [Real Python string formatting tutorial](https://realpython.com/python-string-formatting/) |\n", - "\n", - "- **Time to learn**: 30 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "\n", - "For the examples on this page, we import three modules from the Python Standard Library, as well as one third-party module. The import syntax used here, as well as a discussion on this syntax and an overview of these modules, can be found in the next section." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Python Standard Library packages\n", - "# We'll discuss below WHY we alias the packages this way\n", - "import datetime as dt\n", - "import math\n", - "import time as tm\n", - "\n", - "# Third-party package for time zone handling, we'll discuss below!\n", - "import pytz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## `Time` Versus `Datetime` modules \n", - "\n", - "### Some core terminology\n", - "\n", - "Every Python installation comes with a Standard Library, which includes many helpful modules; in these examples, we cover the [time](https://docs.python.org/3/library/time.html) and [datetime](https://docs.python.org/3/library/datetime.html) modules. Unfortunately, the use of dates and times in Python can be disorienting. There are many different terms used in Python relating to dates and times, and many such terms apply to multiple scopes, such as modules, classes, and functions. For example:\n", - "\n", - "- `datetime` **module** has a `datetime` **class**\n", - "- `datetime` **module** has a `time` **class**\n", - "- `datetime` **module** has a `date` **class**\n", - "- `time` **module** has a `time` function, which returns (almost always) [Unix time](#What-is-Unix-Time?)\n", - "- `datetime` **class** has a `date` method, which returns a `date` object\n", - "- `datetime` **class** has a `time` method, which returns a `time` object\n", - "\n", - "This confusion can be partially alleviated by aliasing our imported modules, we did above:\n", - "\n", - "```\n", - "import datetime as dt\n", - "import time as tm\n", - "```\n", - "\n", - "We can now reference the `datetime` module (aliased to `dt`) and `datetime` class unambiguously." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pisecond = dt.datetime(2021, 3, 14, 15, 9, 26)\n", - "print(pisecond)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our variable `pisecond` now stores a particular date and time, which just happens to be $\\pi$-day 2021 down to the nearest second (3.1415926...)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "now = tm.time()\n", - "print(now)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The variable `now` holds the current time in seconds since January 1, 1970 00:00 UTC. For more information on this important, but seemingly esoteric time format, see the section on this page called \"[What is Unix Time](#What-is-Unix-Time?)\". In addition, if you are not familiar with UTC, there is a section on this page called \"[What is UTC](#What-is-UTC?)\"." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `time` module\n", - "\n", - "The `time` module is well-suited for measuring [Unix time](#What-is-Unix-Time?). For example, when you are calculating how long it takes a Python function to run, you can employ the `time()` function, which can be found in the `time` module, to obtain Unix time before and after the function completes. You can then take the difference of those two times to determine how long the function was running. (Measuring the runtime of a block of code this way is known as \"benchmarking\".)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "start = tm.time()\n", - "tm.sleep(1) # The sleep function will stop the program for n seconds\n", - "end = tm.time()\n", - "diff = end - start\n", - "print(f\"The benchmark took {diff} seconds\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " You can use the `timeit` module and the `timeit` Jupyter magic for more accurate benchmarking. Documentation on these can be found here.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### What is Unix Time?\n", - "\n", - "Unix time is an example of system time, which is how a computer tracks the passage of time. Computers do not inherently know human representations of time; as such, they store time as a large binary number, indicating a number of time units after a set date and time. This is much easier for a computer to keep track of. In the case of Unix time, the time unit is seconds, and the set date and time is the epoch. Therefore, Unix time is the number of seconds since the epoch. The epoch is defined as January 1, 1970 00:00 [UTC](#What-is-UTC?). This is quite confusing for humans, but again, computers store time in a way that makes sense for them. It is represented \"under the hood\" as a [floating point number](https://en.wikipedia.org/wiki/Floating_point) which is how computers represent real (ℝ) numbers." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `datetime` module\n", - "\n", - "The `datetime` module handles time with the Gregorian calendar (the calendar we, as humans, are familiar with); it is independent of Unix time. The `datetime` module uses an [object-oriented](#Thirty-second-introduction-to-Object-Oriented-programming) approach; it contains the `date`, `time`, `datetime`, `timedelta`, and `tzinfo` classes.\n", - "\n", - "- `date` class represents the day, month, and year\n", - "- `time` class represents the time of day\n", - "- `datetime` class is a combination of the `date` and `time` classes\n", - "- `timedelta` class represents a time duration\n", - "- `tzinfo` class represents time zones, and is an abstract class.\n", - "\n", - "The `datetime` module is effective for:\n", - "\n", - "- performing date and time arithmetic and calculating time duration\n", - "- reading and writing date and time strings with various formats\n", - "- handling time zones (with the help of third-party libraries)\n", - "\n", - "The `time` and `datetime` modules overlap in functionality, but in your geoscientific work, you will probably be using the `datetime` module more than the `time` module." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We'll delve into more details below, but here's a quick example of writing out our `pisecond` datetime object as a formatted string. Suppose we wanted to write out just the date, and write it in the _month/day/year_ format typically used in the US. We can do this using the `strftime()` method. This method formats datetime objects using format specifiers. An example of its usage is shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('Pi day occurred on:', pisecond.strftime(format='%m/%d/%Y'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Reading and writing dates and times\n", - "\n", - "### Parsing lightning data timestamps with the `datetime.strptime` method\n", - "\n", - "In this example, we are analyzing [US NLDN lightning data](https://ghrc.nsstc.nasa.gov/uso/ds_docs/vaiconus/vaiconus_dataset.html). Here is a sample row of data:\n", - "\n", - " 06/27/07 16:18:21.898 18.739 -88.184 0.0 kA 0 1.0 0.4 2.5 8 1.2 13 G\n", - "\n", - "Part of the task involves parsing the `06/27/07 16:18:21.898` time string into a `datetime` object. (Although it is outside the scope of this page's tutorial, a full description of this lightning data format can be found [here](https://ghrc.nsstc.nasa.gov/uso/ds_docs/vaiconus/vaiconus_dataset.html#a6).) In order to parse this string or others that follow the same format, you will need to employ the [datetime.strptime()](https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime) method from the `datetime` module. This method takes two arguments: \n", - "1. the date/time string you wish to parse\n", - "2. the format which describes exactly how the date and time are arranged. \n", - "\n", - "[The full range of formatting options for strftime() and strptime() is described in the Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior). In most cases, finding the correct formatting options inherently takes some degree of experimentation to get right. This is a situation where Python shines; you can use the IPython interpreter, or a Jupyter notebook, to quickly test numerous formatting options. Beyond the official documentation, Google and [Stack Overflow](https://stackoverflow.com/) are your friends in this process. \n", - "\n", - "After some trial and error (as described above), you can find that, in this example, the format string `'%m/%d/%y %H:%M:%S.%f'` will convert the date and time in the data to the correct format." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "strike_time = dt.datetime.strptime('06/27/07 16:18:21.898', '%m/%d/%y %H:%M:%S.%f')\n", - "# print strike_time to see if we have properly parsed our time\n", - "print(strike_time)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Example usage of the `datetime` object\n", - "\n", - "Why did we bother doing this? This is a deceptively simple example; it may appear that we only took the string `06/27/07 16:18:21.898` and reformatted it to `2007-06-27 16:18:21.898000`.\n", - "\n", - "However, our new variable, `strike_time`, is in fact a `datetime` object that we can manipulate in many useful ways. \n", - "\n", - "Here are a few quick examples of the advantages of a datetime object:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Controlling the output format with `strftime()`\n", - "\n", - "The following example shows how to write out the time only, without a date, in a particular format:\n", - "```\n", - "16h 18m 21s\n", - "```\n", - "\n", - "We can do this with the [datetime.strftime()](https://docs.python.org/2/library/datetime.html#datetime.date.strftime) method, which takes a format identical to the one we employed for `strptime()`. After some trial and error from the IPython interpreter, we arrive at `'%Hh %Mm %Ss'`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(strike_time.strftime(format='%Hh %Mm %Ss'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### A simple query of just the year:\n", - "\n", - "Here's a useful shortcut that doesn't even need a format specifier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "strike_time.year" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This works because the `datetime` object stores the data as individual attributes: \n", - "`year`, `month`, `day`, `hour`, `minute`, `second`, `microsecond`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### See how many days have elapsed since the strike:\n", - "\n", - "This example shows how to find the number of days since an event; in this case, the lightning strike described earlier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "(dt.datetime.now() - strike_time).days" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The above example illustrates some simple arithmetic with `datetime` objects. This commonly-used feature will be covered in more detail in the next section." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Calculating coastal tides with the `timedelta` class\n", - "\n", - "In these examples, we will look at current data pertaining to coastal tides during a [tropical cyclone storm surge](http://www.nhc.noaa.gov/surge/).\n", - "\n", - "The [lunar day](http://oceanservice.noaa.gov/education/kits/tides/media/supp_tide05.html) is 24 hours and 50 minutes; there are two low tides and two high tides in that time duration. If we know the time of the current high tide, we can easily calculate the occurrence of the next low and high tides by using the [timedelta class](https://docs.python.org/3/library/datetime.html#timedelta-objects). (In reality, the *exact time* of tides is influenced by local coastal effects, in addition to the laws of celestial mechanics, but we will ignore that fact for this exercise.)\n", - "\n", - "The `timedelta` class is initialized by supplying time duration, usually supplied with [keyword arguments](https://docs.python.org/3/glossary.html#term-argument), to clearly express the length of time. The `timedelta` class allows you to perform arithmetic with dates and times using standard operators (i.e., `+`, `-`, `*`, `/`). You can use these operators with a `timedelta` object, and either another `timedelta` object, a datetime object, or a numeric literal, to obtain objects representing new dates and times.\n", - "\n", - "This convenient language feature is known as [operator overloading](https://en.wikipedia.org/wiki/Operator_overloading), and is another example of Python offering built-in functionality to make programming easier. (In some other languages, such as Java, you would have to call a method to perform such operations, which significantly obfuscates the code.) \n", - "\n", - "In addition, you can use these arithmetic operators with two datetime objects, as shown above with [lightning-strike data](#See-how-many-days-have-elapsed-since-the-strike:), to create `timedelta` objects. Let's examine all these features in the following code block." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "high_tide = dt.datetime(2016, 6, 1, 4, 38, 0)\n", - "lunar_day = dt.timedelta(hours=24, minutes=50)\n", - "tide_duration = lunar_day / 4 # Here we do some arithmetic on the timedelta object!\n", - "next_low_tide = (\n", - " high_tide + tide_duration\n", - ") # Here we add a timedelta object to a datetime object\n", - "next_high_tide = high_tide + (2 * tide_duration) # and so on\n", - "tide_length = next_high_tide - high_tide\n", - "print(f\"The time between high and low tide is {tide_duration}.\")\n", - "print(f\"The current high tide is {high_tide}.\")\n", - "print(f\"The next low tide is {next_low_tide}.\")\n", - "print(f\"The next high tide {next_high_tide}.\")\n", - "print(f\"The tide length is {tide_length}.\")\n", - "print(f\"The type of the 'tide_length' variable is {type(tide_length)}.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To illustrate that the difference of two times yields a `timedelta` object, we can use a built-in Python function called `type()`, which returns the type of its argument. In the above example, we call `type()` in the last `print` statement, and it returns the type of `timedelta`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dealing with Time Zones\n", - "\n", - "Time zones can be a source of confusion and frustration in geoscientific data and in computer programming in general. Core date and time libraries in various programming languages, including Python, inevitably have design flaws, relating to time zones, date and time formatting, and other inherently complex issues. Third-party libraries are often created to fix the limitations of the core libraries, but this approach is frequently unsuccessful. To avoid time-zone-related issues, it is best to handle data in UTC; if data cannot be handled in UTC, efforts should be made to consistently use the same time zone for all data. However, this is not always possible; events such as severe weather are expected to be reported in a local time zone, which is not always consistent.\n", - "\n", - "### What is UTC?\n", - "\n", - "\"[UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time)\" is a combination of the French and English abbreviations for Coordinated Universal Time. It is, in practice, equivalent to Greenwich Mean Time (GMT), the time zone at 0 degrees longitude. (The prime meridian, 0 degrees longitude, runs through Greenwich, a district of London, England.) In geoscientific data, times are often in UTC, although you should always verify that this is actually true to avoid time zone issues.\n", - "\n", - "### Time Zone Naive Versus Time Zone Aware `datetime` Objects\n", - "\n", - "When you create `datetime` objects in Python, they are \"time zone naive\", or, if the subject of time zones is assumed, simply \"naive\". This means that they are unaware of the time zone of the date and time they represent; time zone naive is the opposite of time zone aware. In many situations, you can happily go forward without this detail getting in the way of your work. As the [Python documentation states](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects):\n", - ">Naive objects are easy to understand and to work with, at the cost of ignoring some aspects of reality. \n", - "\n", - "However, if you wish to convey time zone information, you will have to make your `datetime` objects time zone aware. The `datetime` library is able to easily convert the time zone to UTC, also converting the object to a time zone aware state, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "naive = dt.datetime.now()\n", - "aware = dt.datetime.now(dt.timezone.utc)\n", - "print(f\"I am time zone naive {naive}.\")\n", - "print(f\"I am time zone aware {aware}.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that `aware` has `+00:00` appended at the end, indicating zero hours offset from UTC.\n", - "\n", - "Our `naive` object shows the local time on whatever computer was used to run this code. If you're reading this online, chances are the code was executed on a cloud server that already uses UTC. If this is the case, `naive` and `aware` will differ only at the microsecond level, due to round-off error.\n", - "\n", - "In the code above, we used `dt.timezone.utc` to initialize the UTC timezone for our `aware` object. Unfortunately, at this time, the Python Standard Library does not fully support initializing datetime objects with arbitrary time zones; it also does not fully support conversions between time zones for datetime objects. However, there exist third-party libraries that provide some of this functionality; one such library is covered below." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Full time zone support with the `pytz` module\n", - "\n", - "For improved handling of time zones in Python, you will need the third-party [pytz](https://pypi.org/project/pytz/) module, whose classes build upon, or, in object-oriented programming terms, inherit from, classes from the `datetime` module.\n", - "\n", - "In this next example, we repeat the above exercise, but this time, we use a method from the `pytz` module to initialize the `aware` object in a different time zone:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "naive = dt.datetime.now()\n", - "aware = dt.datetime.now(pytz.timezone('US/Mountain'))\n", - "print(f\"I am time zone naive: {naive}.\")\n", - "print(f\"I am time zone aware: {aware}.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `pytz.timezone()` method takes a time zone string; if this string is formatted correctly, the method returns a `tzinfo` object, which can be used when making a datetime object time zone aware. This initializes the time zone for the newly aware object to a specific time zone matching the time zone string. The `-06:00` indicates that we are now operating in a time zone six hours behind UTC.\n", - "\n", - "### Print Time with a Different Time Zone\n", - "\n", - "If you have data that are in UTC, and wish to convert them to another time zone (in this example, US Mountain Time Zone), you will again need to make use of the `pytz` module.\n", - "\n", - "First, we will create a new datetime object with the [utcnow()](https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow) method. Despite the name of this method, the newly created object is time zone naive. Therefore, we must invoke the object's [replace()](https://docs.python.org/3/library/datetime.html#datetime.datetime.replace) method and specify UTC with a `tzinfo` object in order to make the object time zone aware. As described above, we can use the `pytz` module's timezone() method to create a new `tzinfo` object, again using the time zone string 'US/Mountain' (US Mountain Time Zone). To convert the datetime object `utc` from UTC to Mountain Time, we can then run the [astimezone()](https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone) method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "utc = dt.datetime.utcnow().replace(tzinfo=pytz.utc)\n", - "print(\"The UTC time is {}.\".format(utc.strftime('%B %d, %Y, %-I:%M%p')))\n", - "mountaintz = pytz.timezone(\"US/Mountain\")\n", - "ny = utc.astimezone(mountaintz)\n", - "print(\"The 'US/Mountain' time is {}.\".format(ny.strftime('%B %d, %Y, %-I:%M%p')))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the above example, we also use the `strftime()` method to format the date and time string in a human-friendly format." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "\n", - "The Python Standard Library contains several modules for dealing with date and time data. We saw how we can avoid some name ambiguities by aliasing the module names; this can be done with import statements like `import datetime as dt` and `import time as tm`. The `tm.time()` method just returns the current [Unix time](#What-is-Unix-Time?) in seconds -- which can be useful for measuring elapsed time, but not all that useful for working with geophysical data.\n", - "\n", - "The `datetime` module contains various classes for storing, converting, comparing, and formatting date and time data on the Gregorian calendar. We saw how we can parse data files with date and time strings into `dt.datetime` objects using the `dt.datetime.strptime()` method. We also saw how to perform arithmetic using date and time data; this uses the `dt.timedelta` class to represent intervals of time.\n", - "\n", - "Finally, we looked at using the third-party [pytz](https://pypi.org/project/pytz/) module to handle time zone awareness and conversions.\n", - "\n", - "### What's Next?\n", - "\n", - "In subsequent tutorials, we will dig deeper into different time and date formats, and discuss how they are handled by important Python modules such as Numpy, Pandas, and Xarray." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and References\n", - "\n", - "This page was based on and adapted from material in [Unidata's Python Training](https://unidata.github.io/python-training/python/times_and_dates/).\n", - "\n", - "For further reading on these modules, take a look at the official documentation for:\n", - "- [time](https://docs.python.org/3/library/time.html)\n", - "- [datetime](https://docs.python.org/3/library/datetime.html)\n", - "- [pytz](https://pypi.org/project/pytz/)\n", - "\n", - "For more information on Python string formatting, try:\n", - "- [Python string documentation](https://docs.python.org/3/library/string.html)\n", - "- RealPython's [string formatting tutorial](https://realpython.com/python-string-formatting/) (nicely written)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/matplotlib.md b/_preview/419/_sources/core/matplotlib.md deleted file mode 100644 index 5312e9bdf..000000000 --- a/_preview/419/_sources/core/matplotlib.md +++ /dev/null @@ -1,29 +0,0 @@ -![Matplotlib logo](https://matplotlib.org/stable/_images/sphx_glr_logos2_003.png) - -# Matplotlib - -[Matplotlib](https://matplotlib.org) is the go-to library for plotting within Python. Numerous packages and libraries build off of Matplotlib, making it the de facto standard Python plotting package. If you were to learn a single plotting tool to keep in your toolbox, this is it. - -## Why Matplotlib? - -Matplotlib is a plotting library for Python and is often the first plotting package Python learners encounter. You may be wondering, "Why learn Matplotlib? Why not [Seaborn](https://seaborn.pydata.org) or another plotting library first?" - -The simple answer to the much-asked question of "why Matplotlib?" is that it is extremely popular; in fact, Matplotlib is one of the most popular Python packages. Because of its history as Python's "go-to" plotting package, most other open source plotting libraries, including Seaborn, are built on top of Matplotlib; thus, these more specialized plotting packages inherit some of Matplotlib's capabilities, syntax, and limitations. Thus, you will find it useful to be familiar with Matplotlib when learning other plotting libraries. - -Matplotlib supports a variety of output formats, chart types, and interactive options, and runs well on most operating systems and graphic backends. The key features of Matplotlib are its extensibility and the [extensive documentation](https://matplotlib.org) available to the community. All of these things contribute to Matplotlib's popularity, which is the answer to the question of "Why Matplotlib", and the reason Matplotlib is the first plotting package we will introduce you to in this book. - -## In this section - -In this section of Pythia Foundations, you will find tutorials on basic plotting with [Matplotlib](https://matplotlib.org). - -From the [Matplotlib documentation](https://matplotlib.org), "Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python." - -Currently, Pythia Foundations provides a basic introduction to Matplotlib, as well as: - -- Histograms -- Piecharts -- Animations -- Annotations -- Colorbars -- Contour plots -- Customizing layouts diff --git a/_preview/419/_sources/core/matplotlib/annotations-colorbars-layouts.ipynb b/_preview/419/_sources/core/matplotlib/annotations-colorbars-layouts.ipynb deleted file mode 100644 index 27250dde4..000000000 --- a/_preview/419/_sources/core/matplotlib/annotations-colorbars-layouts.ipynb +++ /dev/null @@ -1,687 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a2d0abc7-ffd8-483e-87ae-bb169c5bcecf", - "metadata": {}, - "source": [ - "![Matplotlib logo](https://matplotlib.org/stable/_images/sphx_glr_logos2_003.png)" - ] - }, - { - "cell_type": "markdown", - "id": "2583ef82-33dc-4df5-9f6d-f357d72f0b81", - "metadata": {}, - "source": [ - "# Annotations, Colorbars, and Advanced Layouts\n", - "\n", - "---\n", - "## Overview\n", - "\n", - "In this section we explore methods for customizing plots. The following topics will be covered:\n", - "\n", - "1. Adding annotations\n", - "1. Rendering equations\n", - "1. Colormap overview \n", - "1. Basic colorbars \n", - "1. Shared colorbars\n", - "1. Custom colorbars\n", - "1. Mosaic subplots" - ] - }, - { - "cell_type": "markdown", - "id": "94250818-a557-4717-ae71-6aa45b9f212b", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "\n", - "| Concepts | Importance |\n", - "| --- | --- |\n", - "| [NumPy Basics](../numpy/numpy-basics) | Necessary |\n", - "| [Matplotlib Basics](matplotlib-basics) | Necessary |\n", - "\n", - "- **Time to learn**: *30-40 minutes*" - ] - }, - { - "cell_type": "markdown", - "id": "9deb8579-2995-46b4-a82f-1ab79a67155c", - "metadata": {}, - "source": [ - "## Imports\n", - "Here, we import the `matplotlib.pyplot` interface and `numpy`, in addition to the `scipy` statistics package (`scipy.stats`) for generating sample data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "00b72a52-d8e5-48e1-ac4c-35c2e3217de5", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import scipy.stats as stats\n", - "from matplotlib.colors import LinearSegmentedColormap, ListedColormap, Normalize" - ] - }, - { - "cell_type": "markdown", - "id": "a4a423a8-5692-448f-aa78-3d16d3ace19d", - "metadata": {}, - "source": [ - "## Create Some Sample Data\n", - "By using `scipy.stats`, the Scipy statistics package described above, we can easily create a data array containing a normal distribution. We can plot these data points to confirm that the correct distribution was generated. The generated sample data will then be used later in this section. The code and sample plot for this data generation are as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cd97b1e4-0b10-4099-b288-0c9cb7624a11", - "metadata": {}, - "outputs": [], - "source": [ - "mu = 0\n", - "variance = 1\n", - "sigma = np.sqrt(variance)\n", - "\n", - "x = np.linspace(mu - 3 * sigma, mu + 3 * sigma, 200)\n", - "pdf = stats.norm.pdf(x, mu, sigma)\n", - "\n", - "plt.plot(x, pdf);" - ] - }, - { - "cell_type": "markdown", - "id": "fbe53c79-98d1-4cda-bea9-4cf532ce835e", - "metadata": {}, - "source": [ - "## Adding Annotations\n", - "A common part of many people's workflows is adding annotations. A rough definition of 'annotation' is 'a note of explanation or comment added to text or a diagram'.\n", - "\n", - "We can add an annotation to a plot using `plt.text`. This method takes the x and y data coordinates at which to draw the annotation (as floating-point values), and the string containing the annotation text." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1b5598da-caf6-4bc6-aa59-e74954b77bce", - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(x, pdf)\n", - "plt.text(0, 0.05, 'here is some text!');" - ] - }, - { - "cell_type": "markdown", - "id": "1ab0a677-90c8-43f9-9d0d-c2f9de698edb", - "metadata": {}, - "source": [ - "## Rendering Equations" - ] - }, - { - "cell_type": "markdown", - "id": "9e2bc873-0592-4813-81ab-79e3ea7f5855", - "metadata": {}, - "source": [ - "We can also add annotations with **equation formatting**, by using LaTeX syntax. The key is to use strings in the following format:\n", - "\n", - "```python\n", - "r'$some_equation$'\n", - "```\n", - "\n", - "Let's run an example that renders the following equation as an annotation:\n", - "\n", - "$$f(x) = \\frac{1}{\\mu\\sqrt{2\\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2}$$\n", - "\n", - "The next code block and plot demonstrate rendering this equation as an annotation.\n", - "\n", - "If you are interested in learning more about LaTeX syntax, check out [their official documentation](https://latex-tutorial.com/tutorials/amsmath/).\n", - "\n", - "Furthermore, if the code is being executed in a Jupyter notebook run interactively (e.g., on Binder), you can double-click on the cell to see the LaTeX source for the rendered equation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9ae9caab-af46-42f5-ac36-47cafbcdaf68", - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(x, pdf)\n", - "\n", - "plt.text(\n", - " -1,\n", - " 0.05,\n", - " r'$f(x) = \\frac{1}{\\mu\\sqrt{2\\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2}$',\n", - ");" - ] - }, - { - "cell_type": "markdown", - "id": "aad54e23-b488-4437-89ad-55e14e410f90", - "metadata": {}, - "source": [ - "As you can see, the equation was correctly rendered in the plot above. However, the equation appears quite small. We can increase the size of the text using the `fontsize` keyword argument, and center the equation using the `ha` (horizontal alignment) keyword argument.\n", - "\n", - "The following example illustrates the use of these keyword arguments, as well as creating a legend containing LaTeX notation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f915a43f-dd59-462a-a52f-f2d39e53f6cd", - "metadata": {}, - "outputs": [], - "source": [ - "fstr = r'$f(x) = \\frac{1}{\\mu\\sqrt{2\\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2}$'\n", - "\n", - "plt.plot(x, pdf, label=r'$\\mu=0, \\,\\, \\sigma^2 = 1$')\n", - "plt.text(0, 0.05, fstr, fontsize=15, ha='center')\n", - "plt.legend();" - ] - }, - { - "cell_type": "markdown", - "id": "79bc5d01-186c-41df-b645-73d49cfc85d1", - "metadata": {}, - "source": [ - "### Add a Box Around the Text" - ] - }, - { - "cell_type": "markdown", - "id": "210d8d70-e14c-47fa-a710-1d04e84496f4", - "metadata": {}, - "source": [ - "To improve readability, we can also add a box around the equation text. This is done using `bbox`.\n", - "\n", - "`bbox` is a keyword argument in `plt.text` that creates a box around text. It takes a dictionary that specifies options, behaving like additional keyword arguments inside of the `bbox` argument. In this case, we use the following dictionary keys:\n", - "* a rounded box style (`boxstyle = 'round'`)\n", - "* a light grey facecolor (`fc = 'lightgrey'`)\n", - "* a black edgecolor (`ec = 'k'`)\n", - "\n", - "This example demonstrates the correct use of `bbox`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3be0cb9a-8058-4357-97e4-089d556b3194", - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 8))\n", - "plt.plot(x, pdf)\n", - "\n", - "fstr = r'$f(x) = \\frac{1}{\\mu\\sqrt{2\\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2}$'\n", - "plt.text(\n", - " 0,\n", - " 0.05,\n", - " fstr,\n", - " fontsize=18,\n", - " ha='center',\n", - " bbox=dict(boxstyle='round', fc='lightgrey', ec='k'),\n", - ")\n", - "\n", - "plt.xticks(fontsize=16)\n", - "plt.yticks(fontsize=16)\n", - "\n", - "plt.title(\"Normal Distribution with SciPy\", fontsize=24);" - ] - }, - { - "cell_type": "markdown", - "id": "91f4b661-131f-4d29-925f-0028f987be14", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "60564465-4b40-467b-a1a5-d10c4378329c", - "metadata": {}, - "source": [ - "## Colormap Overview\n", - "\n", - "Colormaps are a visually appealing method of looking at visualized data in a new and different way. They associate specific values with hues, using color to ease rapid understanding of plotted data; for example, displaying hotter temperatures as red and colder temperatures as blue." - ] - }, - { - "cell_type": "markdown", - "id": "b0179902-36d5-4b51-894c-f8aad2d92ad8", - "metadata": {}, - "source": [ - "### Classes of colormaps\n", - "\n", - "There are four different classes of colormaps, and many individual maps are contained in each class. To view some examples for each class, use the dropdown arrow next to the class name below.\n", - "\n", - "
\n", - " 1. Sequential: These colormaps incrementally increase or decrease in lightness and/or saturation of color. In general, they work best for ordered data. \n", - "\n", - "![Perceptually Sequential](images/perceptually-sequential.png)\n", - "\n", - "![Sequential](images/sequential.png)\n", - "\n", - "![Sequential2](images/sequential2.png)\n", - "\n", - "![Perceptually Sequential](images/ps.png)\n", - "\n", - "![Sequential](images/s1.png)\n", - "\n", - "![Sequential2](images/s2.png)\n", - "
\n", - "\n", - "
\n", - " 2. Diverging: These colormaps contain two colors that change in lightness and/or saturation in proportion to distance from the middle, and an unsaturated color in the middle. They are almost always used with data containing a natural zero point, such as sea level. \n", - "\n", - "![Diverging](images/diverging.png)\n", - "\n", - "![Diverging](images/d.png)\n", - "
\n", - "\n", - "
\n", - " 3. Cyclic: These colormaps have two different colors that change in lightness and meet in the middle, and unsaturated colors at the beginning and end. They are usually best for data values that wrap around, such as longitude. \n", - "\n", - "![Cyclic](images/cyclic.png)\n", - "\n", - "![Cyclic](images/c.png)\n", - "
\n", - "\n", - "
\n", - " 4. Qualitative: These colormaps have no pattern, and are mostly bands of miscellaneous colors. You should only use these colormaps for unordered data without relationships. \n", - "\n", - "![Qualitative](images/qualitative.png)\n", - "\n", - "![Miscellanous](images/misc.png)\n", - "\n", - "![Miscellanous](images/m.png)\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "5ce3b4c7-1186-4067-9b34-552382bf614e", - "metadata": {}, - "source": [ - "### Other considerations\n", - "\n", - "There is a lot of info about choosing colormaps that could be its own tutorial. Two important considerations:\n", - "1. Color-blind friendly patterns: By using colormaps that do not contain both red and green, you can help people with the most common form of color blindness read your data plots more easily. The GeoCAT examples gallery has a section about [picking better colormaps](https://geocat-examples.readthedocs.io/en/latest/gallery/index.html#colors) that covers this issue in greater detail.\n", - "1. Grayscale conversion: It is not too uncommon for a plot originally rendered in color to be converted to black-and-white (monochrome grayscale). This reduces the usefulness of specific colormaps, as shown below.\n", - "\n", - "![hsv colormap in grayscale](images/hsv2gray.png)\n", - "\n", - "- For more information on these concerns, as well as colormap choices in general, see the documentation page [Choosing Colormaps in Matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html). " - ] - }, - { - "cell_type": "markdown", - "id": "66f8b410-ce74-47ad-99be-0b7c670c6c05", - "metadata": {}, - "source": [ - "## Basic Colorbars" - ] - }, - { - "cell_type": "markdown", - "id": "cb557718-d4ee-41c9-834c-ceddf2e3329a", - "metadata": {}, - "source": [ - "Before we look at a colorbar, let's generate some fake X and Y data using `numpy.random`, and set a number of bins for a histogram:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "49665676-e0db-425d-9ac2-9a0cab084297", - "metadata": {}, - "outputs": [], - "source": [ - "npts = 1000\n", - "nbins = 15\n", - "\n", - "x = np.random.normal(size=npts)\n", - "y = np.random.normal(size=npts)" - ] - }, - { - "cell_type": "markdown", - "id": "614c5189-a563-4856-a900-26bf3dcc849a", - "metadata": {}, - "source": [ - "Now we can use our fake data to plot a 2-D histogram with the number of bins set above. We then add a colorbar to the plot, using the default colormap `viridis`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bf3694f1-e8ed-40de-a50a-7e85b179868c", - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure()\n", - "ax = plt.gca()\n", - "\n", - "plt.hist2d(x, y, bins=nbins, density=True)\n", - "plt.colorbar();" - ] - }, - { - "cell_type": "markdown", - "id": "dc8e6e2b-3850-4379-bf18-d78ee01739dc", - "metadata": {}, - "source": [ - "We can change which colormap to use by setting the keyword argument `cmap = 'colormap_name'` in the plotting function call. This sets the colormap not only for the plot, but for the colorbar as well. In this case, we use the `magma` colormap:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16c61ae2-14f6-4b14-8360-c832a46a42b1", - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure()\n", - "ax = plt.gca()\n", - "\n", - "plt.hist2d(x, y, bins=nbins, density=True, cmap='magma')\n", - "plt.colorbar();" - ] - }, - { - "cell_type": "markdown", - "id": "7c80a5af-364b-4eda-87c0-10a709b1f32b", - "metadata": {}, - "source": [ - "## Shared Colorbars\n", - "Oftentimes, you are plotting multiple subplots, or multiple `Axes` objects, simultaneously. In these scenarios, you can create colorbars that span multiple plots, as shown in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "affb4b48-1656-4f70-a9d5-e7bfb0002e7b", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)\n", - "\n", - "hist1 = ax[0].hist2d(x, y, bins=15, density=True, vmax=0.18)\n", - "hist2 = ax[1].hist2d(x, y, bins=30, density=True, vmax=0.18)\n", - "\n", - "fig.colorbar(hist1[3], ax=ax, location='bottom')" - ] - }, - { - "cell_type": "markdown", - "id": "1662bf3c", - "metadata": {}, - "source": [ - "You may be wondering why the call to `fig.colorbar` uses the argument `hist1[3]`. The explanation is as follows: `hist1` is a tuple returned by `hist2d`, and `hist1[3]` contains a `matplotlib.collections.QuadMesh` that points to the colormap for the first histogram. To make sure that both histograms are using the same colormap with the same range of values, `vmax` is set to 0.18 for both plots. This ensures that both histograms are using colormaps that represent values from 0 (the default for histograms) to 0.18. Because the same data values are used for both plots, it doesn't matter whether we pass in `hist1[3]` or `hist2[3]` to `fig.colorbar`.\n", - "You can learn more about this topic by reviewing the [`matplotlib.axes.Axes.hist2d` documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.hist2d.html)." - ] - }, - { - "cell_type": "markdown", - "id": "84c50862", - "metadata": {}, - "source": [ - "In addition, there are many other types of plots that can also share colorbars. An actual use case that is quite common is to use shared colorbars to compare data between filled contour plots. The `vmin` and `vmax` keyword arguments behave the same way for `contourf` as they do for `hist2d`. However, there is a potential downside to using the `vmin` and `vmax` kwargs. When plotting two different datasets, the dataset with the smaller range of values won't show the full range of colors, even though the colormaps are the same. Thus, it can potentially matter which output from `contourf` is used to make a colorbar. The following examples demonstrate general plotting technique for filled contour plots with shared colorbars, as well as best practices for dealing with some of these logistical issues:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28d4cea3", - "metadata": {}, - "outputs": [], - "source": [ - "x2 = y2 = np.arange(-3, 3.01, 0.025)\n", - "X2, Y2 = np.meshgrid(x2, y2)\n", - "Z = np.sqrt(np.sin(X2) ** 2 + np.sin(Y2) ** 2)\n", - "Z2 = np.sqrt(2 * np.cos(X2) ** 2 + 2 * np.cos(Y2) ** 2)\n", - "\n", - "fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)\n", - "c1 = ax[0].contourf(X2, Y2, Z, vmin=0, vmax=2)\n", - "c2 = ax[1].contourf(X2, Y2, Z2, vmin=0, vmax=2)\n", - "fig.colorbar(c1, ax=ax[0], location='bottom')\n", - "fig.colorbar(c2, ax=ax[1], location='bottom')\n", - "\n", - "fig.suptitle('Shared colormaps on data with different ranges')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5570ebb7", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)\n", - "c1 = ax[0].contourf(X2, Y2, Z, vmin=0, vmax=2)\n", - "c2 = ax[1].contourf(X2, Y2, Z2, vmin=0, vmax=2)\n", - "fig.colorbar(c2, ax=ax, location='bottom')\n", - "\n", - "fig.suptitle('Using the contourf output from the data with a wider range')" - ] - }, - { - "cell_type": "markdown", - "id": "92d072f8-7370-4ea5-92e0-4407cb5905bb", - "metadata": { - "tags": [] - }, - "source": [ - "## Custom Colorbars\n", - "\n", - "Despite the availability of a large number of premade colorbar styles, it can still occasionally be helpful to create your own colorbars.\n", - "\n", - "Below are 2 similar examples of using custom colorbars.\n", - "\n", - "The first example uses a very discrete list of colors, simply named `colors`, and creates a colormap from this list by using the call `ListedColormap`. \n", - "\n", - "The second example uses the function `LinearSegmentedColormap` to create a new colormap, using interpolation and the `colors` list defined in the first example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "308cb21e-7d82-42b9-a02a-0b452d58d4ed", - "metadata": {}, - "outputs": [], - "source": [ - "colors = [\n", - " 'white',\n", - " 'pink',\n", - " 'red',\n", - " 'orange',\n", - " 'yellow',\n", - " 'green',\n", - " 'blue',\n", - " 'purple',\n", - " 'black',\n", - "]\n", - "ccmap = ListedColormap(colors)\n", - "norm = Normalize(vmin=0, vmax=0.18)\n", - "\n", - "fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)\n", - "\n", - "hist1 = ax[0].hist2d(x, y, bins=15, density=True, cmap=ccmap, norm=norm)\n", - "hist2 = ax[1].hist2d(x, y, bins=30, density=True, cmap=ccmap, norm=norm)\n", - "\n", - "cbar = fig.colorbar(hist1[3], ax=ax, location='bottom')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c72b622-ba9b-4fdb-be25-27366eca3872", - "metadata": {}, - "outputs": [], - "source": [ - "cbcmap = LinearSegmentedColormap.from_list(\"cbcmap\", colors)\n", - "\n", - "fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)\n", - "\n", - "hist1 = ax[0].hist2d(x, y, bins=15, density=True, cmap=cbcmap, norm=norm)\n", - "hist2 = ax[1].hist2d(x, y, bins=30, density=True, cmap=cbcmap, norm=norm)\n", - "\n", - "cbar = fig.colorbar(hist1[3], ax=ax, location='bottom')" - ] - }, - { - "cell_type": "markdown", - "id": "ea7f200f", - "metadata": {}, - "source": [ - "### The `Normalize` Class\n", - "Notice that both of these examples contain plotting functions that make use of the `norm` kwarg. This keyword argument takes an object of the `Normalize` class. A `Normalize` object is constructed with two numeric values, representing the start and end of the data. It then linearly normalizes the data in that range into an interval of [0,1]. If this sounds familiar, it is because this functionality was used in a previous histogram example. Feel free to review any previous examples if you need a refresher on particular topics. In this example, the values of the `vmin` and `vmax` kwargs used in `hist2d` are reused as arguments to the `Normalize` class constructor. This sets the values of `vmin` and `vmax` as the starting and ending data values for our `Normalize` object, which is passed to the `norm` kwarg of `hist2d` to normalize the data. There are many different options for normalizing data, and it is important to explicitly specify how you want your data normalized, especially when making a custom colormap.\n", - "\n", - "For information on nonlinear and other complex forms of normalization, review this [Colormap Normalization tutorial](https://matplotlib.org/stable/tutorials/colors/colormapnorms.html#)." - ] - }, - { - "cell_type": "markdown", - "id": "e41f44e0-2c4f-4ce2-abe6-35d20b8c142e", - "metadata": { - "tags": [] - }, - "source": [ - "## Mosaic Subplots\n", - "One of the helpful features recently added to Matplotlib is the `subplot_mosaic` method. This method allows you to specify the structure of your figure using specially formatted strings, and will generate subplots automatically based on that structure.\n", - "\n", - "For example, if we wanted two plots on top, and one on the bottom, we can construct them by passing the following string to `subplot_mosaic`:\n", - "\n", - "```python\n", - "\"\"\n", - "AB\n", - "CC\n", - "\"\"\n", - "```\n", - "\n", - "This creates three `Axes` objects corresponding to three subplots. The subplots `A` and `B` are on top of the subplot `C`, and the `C` subplot spans the combined width of `A` and `B`.\n", - "\n", - "Once we create the subplots, we can access them using the dictionary returned by `subplot_mosaic`. You can specify an `Axes` object (in this example, `your_axis`) in the dictionary (in this example, `axes_dict`) by using the syntax `axes_dict['your_axis']`. A full example of `subplot_mosaic` is as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e080054-ce5c-451d-81f6-c4791a4b2537", - "metadata": {}, - "outputs": [], - "source": [ - "axdict = plt.figure(constrained_layout=True).subplot_mosaic(\n", - " \"\"\"\n", - " AB\n", - " CC\n", - " \"\"\"\n", - ")\n", - "\n", - "histA = axdict['A'].hist2d(x, y, bins=15, density=True, cmap=cbcmap, norm=norm)\n", - "histB = axdict['B'].hist2d(x, y, bins=10, density=True, cmap=cbcmap, norm=norm)\n", - "histC = axdict['C'].hist2d(x, y, bins=30, density=True, cmap=cbcmap, norm=norm)" - ] - }, - { - "cell_type": "markdown", - "id": "7569067a-7c59-46b0-b283-b108094010f1", - "metadata": {}, - "source": [ - "You'll notice there is not a colorbar plotted by default. When constructing the colorbar, we need to specify the following:\n", - "* Which plot to use for the colormapping (ex. `histA`)\n", - "* Which subplots (`Axes` objects) to merge colorbars across (ex. [`histA`, `histB`])\n", - "* Where to place the colorbar (ex. `bottom`)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3077f1b6-adba-411d-a5a5-430600f0e2fa", - "metadata": {}, - "outputs": [], - "source": [ - "axdict = plt.figure(constrained_layout=True).subplot_mosaic(\n", - " \"\"\"\n", - " AB\n", - " CC\n", - " \"\"\"\n", - ")\n", - "\n", - "histA = axdict['A'].hist2d(x, y, bins=15, density=True, cmap=cbcmap, norm=norm)\n", - "histB = axdict['B'].hist2d(x, y, bins=10, density=True, cmap=cbcmap, norm=norm)\n", - "histC = axdict['C'].hist2d(x, y, bins=30, density=True, cmap=cbcmap, norm=norm)\n", - "\n", - "fig.colorbar(histA[3], ax=[axdict['A'], axdict['B']], location='bottom')\n", - "fig.colorbar(histC[3], ax=[axdict['C']], location='right');" - ] - }, - { - "cell_type": "markdown", - "id": "85b884b1-4db7-4d9d-9563-79750dbcfc67", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "2f505b91-cb9a-4175-a1b7-91f501c1e2cc", - "metadata": {}, - "source": [ - "## Summary\n", - "* You can use features in Matplotlib to add text annotations to your plots, including equations in mathematical notation\n", - "* There are a number of considerations to take into account when choosing your colormap\n", - "* You can create your own colormaps with Matplotlib\n", - "* Various subplots and corresponding `Axes` objects in a figure can share colorbars\n", - " \n", - "## Resources and references\n", - "- [Matplotlib text documentation](https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text.set_math_fontfamily)\n", - "- [Matplotlib annotation documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html)\n", - "- [Matplotlib's annotation examples](https://matplotlib.org/stable/tutorials/text/annotations.html#sphx-glr-tutorials-text-annotations-py)\n", - "- [Writing mathematical expressions in Matplotlib](https://matplotlib.org/stable/tutorials/text/mathtext.html)\n", - "- [Mathtext Examples](https://matplotlib.org/stable/gallery/text_labels_and_annotations/mathtext_examples.html#sphx-glr-gallery-text-labels-and-annotations-mathtext-examples-py)\n", - "- [Drawing fancy boxes with Matplotlib](https://matplotlib.org/stable/gallery/shapes_and_collections/fancybox_demo.html)\n", - "- [Plot Types Cheat Sheet](https://lnkd.in/dD5fE8V)\n", - "- [Choosing Colormaps in Matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html)\n", - "- [Making custom colormaps](https://matplotlib.org/stable/tutorials/colors/colormap-manipulation.html)\n", - "- [Complex figure and subplot composition](https://matplotlib.org/stable/tutorials/provisional/mosaic.html#)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "28649673-c9c6-4914-9f60-5c06c25e1e49", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_preview/419/_sources/core/matplotlib/histograms-piecharts-animation.ipynb b/_preview/419/_sources/core/matplotlib/histograms-piecharts-animation.ipynb deleted file mode 100644 index 1974c8e31..000000000 --- a/_preview/419/_sources/core/matplotlib/histograms-piecharts-animation.ipynb +++ /dev/null @@ -1,462 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3d9564ec", - "metadata": {}, - "source": [ - "![Matplotlib logo](https://matplotlib.org/stable/_images/sphx_glr_logos2_003.png)" - ] - }, - { - "cell_type": "markdown", - "id": "e9eb4444", - "metadata": {}, - "source": [ - "# Histograms, Pie Charts, and Animations" - ] - }, - { - "cell_type": "markdown", - "id": "cba92e3a", - "metadata": {}, - "source": [ - "---\n", - "## Overview\n", - "\n", - "In this section we'll explore some more specialized plot types, including:\n", - "\n", - "1. Histograms\n", - "1. Pie Charts\n", - "1. Animations" - ] - }, - { - "cell_type": "markdown", - "id": "56c73537", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [NumPy Basics](../numpy/numpy-basics) | Necessary | |\n", - "| [Matplotlib Basics](matplotlib-basics) | Necessary | |\n", - "\n", - "* **Time to Learn**: 30 minutes" - ] - }, - { - "cell_type": "markdown", - "id": "4440f2b1", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "0702fe7b", - "metadata": {}, - "source": [ - "## Imports" - ] - }, - { - "cell_type": "markdown", - "id": "148180d5", - "metadata": {}, - "source": [ - "Just like in the previous tutorial, we are going to import Matplotlib's `pyplot` interface as `plt`. We must also import `numpy` for working with data arrays." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d16d139c", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "7310773f", - "metadata": {}, - "source": [ - "## Histograms\n" - ] - }, - { - "cell_type": "markdown", - "id": "a5ea8056", - "metadata": {}, - "source": [ - "We can plot a 1-D histogram using most 1-D data arrays.\n", - "\n", - "To get the 1-D data array for this example, we generate example data using NumPy’s normal-distribution random-number generator. For demonstration purposes, we've specified the random seed for reproducibility. The code for this number generation is as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "df424130", - "metadata": {}, - "outputs": [], - "source": [ - "npts = 2500\n", - "nbins = 15\n", - "\n", - "np.random.seed(0)\n", - "x = np.random.normal(size=npts)" - ] - }, - { - "cell_type": "markdown", - "id": "32b9f3bd", - "metadata": {}, - "source": [ - "Now that we have our data array, we can make a histogram using `plt.hist`. In this case, we change the y-axis to represent probability, instead of count; this is performed by setting `density=True`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dfd7c0cc", - "metadata": {}, - "outputs": [], - "source": [ - "plt.hist(x, bins=nbins, density=True)\n", - "plt.title('1D histogram')\n", - "plt.xlabel('Data')\n", - "plt.ylabel('Probability');" - ] - }, - { - "cell_type": "markdown", - "id": "84fb255f", - "metadata": {}, - "source": [ - "Similarly, we can make a 2-D histogram, by first generating a second 1-D array, and then calling `plt.hist2d` with both 1-D arrays as arguments:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4ed3325e", - "metadata": {}, - "outputs": [], - "source": [ - "y = np.random.normal(size=npts)\n", - "\n", - "plt.hist2d(x, y, bins=nbins);" - ] - }, - { - "cell_type": "markdown", - "id": "dd5a6bca", - "metadata": {}, - "source": [ - "## Pie Charts" - ] - }, - { - "cell_type": "markdown", - "id": "35bc61ba", - "metadata": {}, - "source": [ - "Matplotlib also has the capability to plot pie charts, by way of `plt.pie`. The most basic implementation uses a 1-D array of wedge 'sizes' (i.e., percent values), as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9399feaa", - "metadata": {}, - "outputs": [], - "source": [ - "x = np.array([25, 15, 20, 40])\n", - "plt.pie(x);" - ] - }, - { - "cell_type": "markdown", - "id": "1cec2e20", - "metadata": {}, - "source": [ - "Typically, you'll see examples where all of the values in the array `x` will sum to 100, but the data values provided to `plt.pie` do not necessarily have to add up to 100. The sum of the numbers provided will be normalized to 1, and the individual values will thereby be converted to percentages, regardless of the actual sum of the values. If this behavior is unwanted or unneeded, you can set `normalize=False`.\n", - "\n", - "If you set `normalize=False`, and the sum of the values of x is less than 1, then a partial pie chart is plotted. If the values sum to larger than 1, a `ValueError` will be raised." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "be883e4e", - "metadata": {}, - "outputs": [], - "source": [ - "x = np.array([0.25, 0.20, 0.40])\n", - "plt.pie(x, normalize=False);" - ] - }, - { - "cell_type": "markdown", - "id": "e747e452", - "metadata": {}, - "source": [ - "Let's do a more complicated example.\n", - "\n", - "Here we create a pie chart with various sizes associated with each color. Labels are derived by capitalizing each color in the array `colors`. Since colors can be specified by strings corresponding to named colors, this allows both the colors and the labels to be set from the same array, reducing code and effort.\n", - "\n", - "If you want to offset one or more wedges for effect, you can use the `explode` keyword argument. The value for this argument must be a list of floating-point numbers with the same length as the number of wedges. The numbers indicate the percentage of offset for each wedge. In this example, each wedge is not offset except for the pink (3rd index)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fa9ecaec", - "metadata": {}, - "outputs": [], - "source": [ - "colors = ['red', 'blue', 'yellow', 'pink', 'green']\n", - "labels = [c.capitalize() for c in colors]\n", - "\n", - "sizes = [1, 3, 5, 7, 9]\n", - "explode = (0, 0, 0, 0.1, 0)\n", - "\n", - "\n", - "plt.pie(sizes, labels=labels, explode=explode, colors=colors, autopct='%1.1f%%');" - ] - }, - { - "cell_type": "markdown", - "id": "c516ce4b", - "metadata": {}, - "source": [ - "## Animations" - ] - }, - { - "cell_type": "markdown", - "id": "46c1acfc", - "metadata": {}, - "source": [ - "Matplotlib offers a single commonly-used animation tool, `FuncAnimation`. This tool must be imported separately through Matplotlib’s animation package, as shown below. You can find more information on animation with Matplotlib at the [official documentation page](https://matplotlib.org/stable/api/animation_api.html)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3879e346", - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib.animation import FuncAnimation" - ] - }, - { - "cell_type": "markdown", - "id": "a31e17ed", - "metadata": {}, - "source": [ - "`FuncAnimation` creates animations by repeatedly calling a function. Using this method involves three main steps:\n", - "\n", - "1. Create an initial state of the plot\n", - "1. Make a function that can \"progress\" the plot to the next frame of the animation\n", - "1. Create the animation using FuncAnimation" - ] - }, - { - "cell_type": "markdown", - "id": "e1abc210", - "metadata": {}, - "source": [ - "For this example, let's create an animated sine wave." - ] - }, - { - "cell_type": "markdown", - "id": "5455b9de", - "metadata": {}, - "source": [ - "### Step 1: Initial State\n", - "In the initial state step, we will define a function called `init`. This function will then create the animation plot in its initial state. However, please note that the successful use of `FuncAnimation` does not technically require such a function; in a later example, creating animations without an initial-state function is demonstrated." - ] - }, - { - "cell_type": "markdown", - "id": "1037bb3b", - "metadata": {}, - "source": [ - "First, we’ll define `Figure` and `Axes` objects. After that, we can create a line-plot object (referred to here as a line) with `plt.plot`. To create the initialization function, we set the line's data to be empty and then return the line.\n", - "\n", - "Please note, this code block will display a blank plot when run as a Jupyter notebook cell." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3feef13d", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "ax.set_xlim(0, 2 * np.pi)\n", - "ax.set_ylim(-1.5, 1.5)\n", - "\n", - "(line,) = ax.plot([], [])\n", - "\n", - "\n", - "def init():\n", - " line.set_data([], [])\n", - " return (line,)" - ] - }, - { - "cell_type": "markdown", - "id": "679981fb", - "metadata": {}, - "source": [ - "### Step 2: Animation Progression Function\n", - "For this step, we create a progression function, which takes an index (usually named `n` or `i`), and returns the corresponding (in other words, `n`-th or `i`-th) frame of the animation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c5b606e2", - "metadata": {}, - "outputs": [], - "source": [ - "def animate(i):\n", - " x = np.linspace(0, 2 * np.pi, 250)\n", - "\n", - " y = np.sin(2 * np.pi * (x - 0.1 * i))\n", - "\n", - " line.set_data(x, y)\n", - "\n", - " return (line,)" - ] - }, - { - "cell_type": "markdown", - "id": "6ef0f128", - "metadata": {}, - "source": [ - "### Step 3: Using `FuncAnimation`\n", - "The last step is to feed the parts we created to `FuncAnimation`. Please note, when using the `FuncAnimation` function, it is important to save the output in a variable, even if you do not intend to use this output later. If you do not, Python’s garbage collector may attempt to save memory by deleting the animation data, and it will be unavailable for later use." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8ecb3760", - "metadata": {}, - "outputs": [], - "source": [ - "anim = FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)" - ] - }, - { - "cell_type": "markdown", - "id": "cbc69df4", - "metadata": {}, - "source": [ - "In order to show the animation in a Jupyter notebook, we have to use the `rc` function. This function must be imported separately, and is used to set specific parameters in Matplotlib. In this case, we need to set the `html` parameter for animation plots to `html5`, instead of the default value of none. The code for this is written as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b464c460", - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import rc\n", - "\n", - "rc('animation', html='html5')\n", - "\n", - "anim" - ] - }, - { - "cell_type": "markdown", - "id": "8be86c41-2ac0-4385-8b3f-70cf0139b19e", - "metadata": {}, - "source": [ - "### Saving an Animation\n", - "\n", - "To save an animation to a file, use the `save()` method of the animation variable, in this case `anim.save()`, as shown below. The arguments are the file name to save the animation to, in this case `animate.gif`, and the writer used to save the file. Here, the animation writer chosen is [Pillow](https://pillow.readthedocs.io/en/stable/index.html), a library for image processing in Python. There are many choices for an animation writer, which are described in detail in the Matplotlib writer documentation. The documentation for the Pillow writer is described on [this page](https://matplotlib.org/stable/api/_as_gen/matplotlib.animation.PillowWriter.html); links to other writer documentation pages are on the left side of the Pillow writer documentation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6a1693bd", - "metadata": {}, - "outputs": [], - "source": [ - "anim.save('animate.gif', writer='pillow');" - ] - }, - { - "cell_type": "markdown", - "id": "85b884b1-4db7-4d9d-9563-79750dbcfc67", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "076b1bf3", - "metadata": {}, - "source": [ - "## Summary\n", - "* Matplotlib supports many different plot types, including the less-commonly-used types described in this section. \n", - "* Some of these lesser-used plot types include histograms and pie charts.\n", - "* This section also covered animation of Matplotlib plots.\n", - "\n", - "\n", - "## What's Next\n", - "The next section introduces [more plotting functionality](annotations-colorbars-layouts), such as annotations, equation rendering, colormaps, and advanced layout.\n", - "\n", - "## Additional Resources\n", - "- [Plot Types Cheat Sheet](https://lnkd.in/dD5fE8V)\n", - "- [Matplotlib Documentation: Basic Pie Charts](https://matplotlib.org/stable/gallery/pie_and_polar_charts/pie_features.html)\n", - "- [Matplotlib Documentation: Histograms](https://matplotlib.org/stable/gallery/statistics/hist.html)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "370d8045-216c-4150-9ce5-5128721b3962", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_preview/419/_sources/core/matplotlib/matplotlib-basics.ipynb b/_preview/419/_sources/core/matplotlib/matplotlib-basics.ipynb deleted file mode 100644 index cf48576ba..000000000 --- a/_preview/419/_sources/core/matplotlib/matplotlib-basics.ipynb +++ /dev/null @@ -1,908 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Matplotlib logo](https://matplotlib.org/stable/_images/sphx_glr_logos2_003.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Matplotlib Basics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "## Overview\n", - "We will cover the basics of using the Matplotlib library to create plots in Python, including a few different plots available within the library. This page is laid out as follows:\n", - "\n", - "1. Why Matplotlib?\n", - "1. Figure and axes\n", - "1. Basic line plots\n", - "1. Labels and grid lines\n", - "1. Customizing colors\n", - "1. Subplots\n", - "1. Scatterplots\n", - "1. Displaying Images\n", - "1. Contour and filled contour plots." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "## Prerequisites\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [NumPy Basics](../numpy/numpy-basics) | Necessary | |\n", - "| MATLAB plotting experience | Helpful | |\n", - "\n", - "* **Time to Learn**: 30 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's import the Matplotlib library's `pyplot` interface; this interface is the simplest way to create new Matplotlib figures. To shorten this long name, we import it as `plt`; this helps keep things short, but clear." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Matplotlib is a Python 2-D plotting library. It is used to produce publication quality figures in a variety of hard-copy formats and interactive environments across platforms.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate test data using `NumPy`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we generate some test data to use for experimenting with plotting:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "times = np.array(\n", - " [\n", - " 93.0,\n", - " 96.0,\n", - " 99.0,\n", - " 102.0,\n", - " 105.0,\n", - " 108.0,\n", - " 111.0,\n", - " 114.0,\n", - " 117.0,\n", - " 120.0,\n", - " 123.0,\n", - " 126.0,\n", - " 129.0,\n", - " 132.0,\n", - " 135.0,\n", - " 138.0,\n", - " 141.0,\n", - " 144.0,\n", - " 147.0,\n", - " 150.0,\n", - " 153.0,\n", - " 156.0,\n", - " 159.0,\n", - " 162.0,\n", - " ]\n", - ")\n", - "temps = np.array(\n", - " [\n", - " 310.7,\n", - " 308.0,\n", - " 296.4,\n", - " 289.5,\n", - " 288.5,\n", - " 287.1,\n", - " 301.1,\n", - " 308.3,\n", - " 311.5,\n", - " 305.1,\n", - " 295.6,\n", - " 292.4,\n", - " 290.4,\n", - " 289.1,\n", - " 299.4,\n", - " 307.9,\n", - " 316.6,\n", - " 293.9,\n", - " 291.2,\n", - " 289.8,\n", - " 287.1,\n", - " 285.8,\n", - " 303.3,\n", - " 310.0,\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Figure and Axes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's make our first plot with Matplotlib. Matplotlib has two core objects: the `Figure` and the `Axes`. The `Axes` object is an individual plot, containing an x-axis, a y-axis, labels, etc.; it also contains all of the various methods we might use for plotting. A `Figure` contains one or more `Axes` objects; it also contains methods for saving plots to files (e.g., PNG, SVG), among other similar high-level functionality. You may find the following diagram helpful:\n", - "\n", - "![anatomy of a figure](https://matplotlib.org/stable/_images/sphx_glr_anatomy_001.png \"anatomy of a figure\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Basic Line Plots\n", - "\n", - "Let's create a `Figure` whose dimensions, if printed out on hardcopy, would be 10 inches wide and 6 inches long (assuming a landscape orientation). We then create an `Axes` object, consisting of a single subplot, on the `Figure`. After that, we call the `Axes` object's `plot` method, using the `times` array for the data along the x-axis (i.e., the independent values), and the `temps` array for the data along the y-axis (i.e., the dependent values).\n", - "\n", - "
\n", - "

Info

\n", - " By default, ax.plot will create a line plot, as seen in the following example: \n", - "
\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create a figure\n", - "fig = plt.figure(figsize=(10, 6))\n", - "\n", - "# Ask, out of a 1x1 grid of plots, the first axes.\n", - "ax = fig.add_subplot(1, 1, 1)\n", - "\n", - "# Plot times as x-variable and temperatures as y-variable\n", - "ax.plot(times, temps);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Labels and Grid Lines" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding labels to an `Axes` object" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we add x-axis and y-axis labels to our `Axes` object, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Add some labels to the plot\n", - "ax.set_xlabel('Time')\n", - "ax.set_ylabel('Temperature')\n", - "\n", - "# Prompt the notebook to re-display the figure after we modify it\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also add a title to the plot and increase the font size:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ax.set_title('GFS Temperature Forecast', size=16)\n", - "\n", - "fig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are many other functions and methods associated with `Axes` objects and labels, but they are too numerous to list here.\n", - "\n", - "Here, we set up another test array of temperature data, to be used later:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_1000 = np.array(\n", - " [\n", - " 316.0,\n", - " 316.3,\n", - " 308.9,\n", - " 304.0,\n", - " 302.0,\n", - " 300.8,\n", - " 306.2,\n", - " 309.8,\n", - " 313.5,\n", - " 313.3,\n", - " 308.3,\n", - " 304.9,\n", - " 301.0,\n", - " 299.2,\n", - " 302.6,\n", - " 309.0,\n", - " 311.8,\n", - " 304.7,\n", - " 304.6,\n", - " 301.8,\n", - " 300.6,\n", - " 299.9,\n", - " 306.3,\n", - " 311.3,\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding labels and a grid" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we call `plot` more than once, in order to plot multiple series of temperature data on the same plot. We also specify the `label` keyword argument to the `plot` method to allow Matplotlib to automatically create legend labels. These legend labels are added via a call to the `legend` method. By utilizing the `grid()` method, we can also add gridlines to our plot." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "ax = fig.add_subplot(1, 1, 1)\n", - "\n", - "# Plot two series of data\n", - "# The label argument is used when generating a legend.\n", - "ax.plot(times, temps, label='Temperature (surface)')\n", - "ax.plot(times, temps_1000, label='Temperature (1000 mb)')\n", - "\n", - "# Add labels and title\n", - "ax.set_xlabel('Time')\n", - "ax.set_ylabel('Temperature')\n", - "ax.set_title('Temperature Forecast')\n", - "\n", - "# Add gridlines\n", - "ax.grid(True)\n", - "\n", - "# Add a legend to the upper left corner of the plot\n", - "ax.legend(loc='upper left');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Customizing colors" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We're not restricted to the default look for plot elements. Most plot elements have style attributes, such as `linestyle` and `color`, that can be modified to customize the look of a plot. For example, the `color` attribute can accept a wide array of color options, including keywords (named colors) like `red` or `blue`, or HTML color codes. Here, we use some different shades of red taken from the Tableau colorset in Matplotlib, by using the `tab:red` option for the color attribute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "ax = fig.add_subplot(1, 1, 1)\n", - "\n", - "# Specify how our lines should look\n", - "ax.plot(times, temps, color='tab:red', label='Temperature (surface)')\n", - "ax.plot(\n", - " times,\n", - " temps_1000,\n", - " color='tab:red',\n", - " linestyle='--',\n", - " label='Temperature (isobaric level)',\n", - ")\n", - "\n", - "# Set the labels and title\n", - "ax.set_xlabel('Time')\n", - "ax.set_ylabel('Temperature')\n", - "ax.set_title('Temperature Forecast')\n", - "\n", - "# Add the grid\n", - "ax.grid(True)\n", - "\n", - "# Add a legend to the upper left corner of the plot\n", - "ax.legend(loc='upper left');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Subplots\n", - "\n", - "The term \"subplots\" refers to working with multiple plots, or panels, in a figure." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we create yet another set of test data, in this case dew-point data, to be used in later examples:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dewpoint = 0.9 * temps\n", - "dewpoint_1000 = 0.9 * temps_1000" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can use subplots to plot this new data alongside the temperature data." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Using add_subplot to create two different subplots within the figure\n", - "We can use the `.add_subplot()` method to add subplots to our figure! This method takes the arguments `(rows, columns, subplot_number)`.\n", - "\n", - "For example, if we want a single row and two columns, we can use the following code block:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "\n", - "# Create a plot for temperature\n", - "ax = fig.add_subplot(1, 2, 1)\n", - "ax.plot(times, temps, color='tab:red')\n", - "\n", - "# Create a plot for dewpoint\n", - "ax2 = fig.add_subplot(1, 2, 2)\n", - "ax2.plot(times, dewpoint, color='tab:green');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also call `plot.subplots()` with the keyword arguments `nrows` (number of rows) and `ncols` (number of columns). This initializes a new `Axes` object, called `ax`, with the specified number of rows and columns. This object also contains a 1-D list of subplots, with a size equal to `nrows` x `ncols`.\n", - "\n", - "You can index this list, using `ax[0].plot()`, for example, to decide which subplot you're plotting to. Here is some example code for this technique:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 6))\n", - "\n", - "ax[0].plot(times, temps, color='tab:red')\n", - "ax[1].plot(times, dewpoint, color='tab:green');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding titles to each subplot\n", - "We can add titles to these plots too; notice that these subplots are titled separately, by calling `ax.set_title` after plotting each subplot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "\n", - "# Create a plot for temperature\n", - "ax = fig.add_subplot(1, 2, 1)\n", - "ax.plot(times, temps, color='tab:red')\n", - "ax.set_title('Temperature')\n", - "\n", - "# Create a plot for dewpoint\n", - "ax2 = fig.add_subplot(1, 2, 2)\n", - "ax2.plot(times, dewpoint, color='tab:green')\n", - "ax2.set_title('Dewpoint');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Using `ax.set_xlim` and `ax.set_ylim` to control the plot boundaries\n", - "\n", - "It is common when plotting data to set the extent (boundaries) of plots, which can be performed by calling `.set_xlim` and `.set_ylim` on the `Axes` object containing the plot or subplot(s):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "\n", - "# Create a plot for temperature\n", - "ax = fig.add_subplot(1, 2, 1)\n", - "ax.plot(times, temps, color='tab:red')\n", - "ax.set_title('Temperature')\n", - "ax.set_xlim(110, 130)\n", - "ax.set_ylim(290, 315)\n", - "\n", - "# Create a plot for dewpoint\n", - "ax2 = fig.add_subplot(1, 2, 2)\n", - "ax2.plot(times, dewpoint, color='tab:green')\n", - "ax2.set_title('Dewpoint')\n", - "ax2.set_xlim(110, 130);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Using `sharex` and `sharey` to share plot limits\n", - "\n", - "You may want to have both subplots share the same x/y axis limits. When setting up a new `Axes` object through a method like `add_subplot`, specify the keyword arguments `sharex=ax` and `sharey=ax`, where `ax` is the `Axes` object with which to share axis limits.\n", - "\n", - "Let's take a look at an example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "\n", - "# Create a plot for temperature\n", - "ax = fig.add_subplot(1, 2, 1)\n", - "ax.plot(times, temps, color='tab:red')\n", - "ax.set_title('Temperature')\n", - "ax.set_ylim(260, 320)\n", - "\n", - "# Create a plot for dewpoint\n", - "ax2 = fig.add_subplot(1, 2, 2, sharex=ax, sharey=ax)\n", - "ax2.plot(times, dewpoint, color='tab:green')\n", - "ax2.set_title('Dewpoint');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Putting this all together" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " If desired, you can move the location of your legend; to do this, specify the loc keyword argument when calling ax.legend().\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "ax = fig.add_subplot(1, 2, 1)\n", - "\n", - "# Specify how our lines should look\n", - "ax.plot(times, temps, color='tab:red', label='Temperature (surface)')\n", - "ax.plot(\n", - " times,\n", - " temps_1000,\n", - " color='tab:red',\n", - " linestyle=':',\n", - " label='Temperature (isobaric level)',\n", - ")\n", - "\n", - "# Add labels, grid, and legend\n", - "ax.set_xlabel('Time')\n", - "ax.set_ylabel('Temperature')\n", - "ax.set_title('Temperature Forecast')\n", - "ax.grid(True)\n", - "ax.legend(loc='upper left')\n", - "ax.set_ylim(257, 312)\n", - "ax.set_xlim(95, 162)\n", - "\n", - "\n", - "# Add our second plot - for dewpoint, changing the colors and labels\n", - "ax2 = fig.add_subplot(1, 2, 2, sharex=ax, sharey=ax)\n", - "ax2.plot(times, dewpoint, color='tab:green', label='Dewpoint (surface)')\n", - "ax2.plot(\n", - " times,\n", - " dewpoint_1000,\n", - " color='tab:green',\n", - " linestyle=':',\n", - " marker='o',\n", - " label='Dewpoint (isobaric level)',\n", - ")\n", - "\n", - "ax2.set_xlabel('Time')\n", - "ax2.set_ylabel('Dewpoint')\n", - "ax2.set_title('Dewpoint Forecast')\n", - "ax2.grid(True)\n", - "ax2.legend(loc='upper left');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Scatterplot\n", - "Some data cannot be plotted accurately as a line plot. Another type of plot that is popular in science is the marker plot, more commonly known as a scatter plot. A simple scatter plot can be created by setting the `linestyle` to `None`, and specifying a marker type, size, color, etc., like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "ax = fig.add_subplot(1, 1, 1)\n", - "\n", - "# Specify no line with circle markers\n", - "ax.plot(temps, temps_1000, linestyle='None', marker='o', markersize=5)\n", - "\n", - "ax.set_xlabel('Temperature (surface)')\n", - "ax.set_ylabel('Temperature (1000 hPa)')\n", - "ax.set_title('Temperature Cross Plot')\n", - "ax.grid(True);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " You can also use the scatter method, which is slower, but will give you more control, such as being able to color the points individually based upon a third variable.\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "ax = fig.add_subplot(1, 1, 1)\n", - "\n", - "# Specify no line with circle markers\n", - "ax.scatter(temps, temps_1000)\n", - "\n", - "ax.set_xlabel('Temperature (surface)')\n", - "ax.set_ylabel('Temperature (1000 hPa)')\n", - "ax.set_title('Temperature Cross Plot')\n", - "ax.grid(True);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's put together the following:\n", - " * Beginning with our code above, add the `c` keyword argument to the `scatter` call; in this case, to color the points by the difference between the temperature at the surface and the temperature at 1000 hPa.\n", - " * Add a 1:1 line to the plot (slope of 1, intercept of zero). Use a black dashed line.\n", - " * Change the colormap to one more suited for a temperature-difference plot.\n", - " * Add a colorbar to the plot (have a look at the Matplotlib documentation for help)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(10, 6))\n", - "ax = fig.add_subplot(1, 1, 1)\n", - "\n", - "ax.plot([285, 320], [285, 320], color='black', linestyle='--')\n", - "s = ax.scatter(temps, temps_1000, c=(temps - temps_1000), cmap='bwr', vmin=-5, vmax=5)\n", - "fig.colorbar(s)\n", - "\n", - "ax.set_xlabel('Temperature (surface)')\n", - "ax.set_ylabel('Temperature (1000 hPa)')\n", - "ax.set_title('Temperature Cross Plot')\n", - "ax.grid(True);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Displaying Images\n", - "\n", - "`imshow` displays the values in an array as colored pixels, similar to a heat map.\n", - "\n", - "Here, we declare some fake data in a bivariate normal distribution, to illustrate the `imshow` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = y = np.arange(-3.0, 3.0, 0.025)\n", - "X, Y = np.meshgrid(x, y)\n", - "Z1 = np.exp(-(X**2) - Y**2)\n", - "Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)\n", - "Z = (Z1 - Z2) * 2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now pass this fake data to `imshow` to create a heat map of the distribution:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "im = ax.imshow(\n", - " Z, interpolation='bilinear', cmap='RdYlGn', origin='lower', extent=[-3, 3, -3, 3]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Contour and Filled Contour Plots\n", - "\n", - "- `contour` creates contours around data.\n", - "- `contourf` creates filled contours around data." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's start with the `contour` method, which, as just mentioned, creates contours around data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "ax.contour(X, Y, Z);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After creating contours, we can label the lines using the `clabel` method, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "c = ax.contour(X, Y, Z, levels=np.arange(-2, 2, 0.25))\n", - "ax.clabel(c);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As described above, the `contourf` (contour fill) method creates filled contours around data, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "c = ax.contourf(X, Y, Z);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As a final example, let's create a heatmap figure with contours using the `contour` and `imshow` methods. First, we use `imshow` to create the heatmap, specifying a colormap using the `cmap` keyword argument. We then call `contour`, specifying black contours and an interval of 0.5. Here is the example code, and resulting figure:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "im = ax.imshow(\n", - " Z, interpolation='bilinear', cmap='PiYG', origin='lower', extent=[-3, 3, -3, 3]\n", - ")\n", - "c = ax.contour(X, Y, Z, levels=np.arange(-2, 2, 0.5), colors='black')\n", - "ax.clabel(c);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "* `Matplotlib` can be used to visualize datasets you are working with.\n", - "* You can customize various features such as labels and styles.\n", - "* There are a wide variety of plotting options available, including (but not limited to):\n", - " * Line plots (`plot`)\n", - " * Scatter plots (`scatter`)\n", - " * Heatmaps (`imshow`)\n", - " * Contour line and contour fill plots (`contour`, `contourf`)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## What's Next?\n", - "In the next section, [more plotting functionality](histograms-piecharts-animation) is covered, such as histograms, pie charts, and animation." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and References\n", - "\n", - "The goal of this tutorial is to provide an overview of the use of the Matplotlib library. It covers creating simple line plots, but it is by no means comprehensive. For more information, try looking at the following documentation:\n", - "- [Matplotlib documentation](http://matplotlib.org)\n", - "- [Matplotlib examples gallery](https://matplotlib.org/stable/gallery/index.html)\n", - "- [GeoCAT examples gallery](https://geocat-examples.readthedocs.io/en/latest/gallery/index.html)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/numpy.md b/_preview/419/_sources/core/numpy.md deleted file mode 100644 index 8229b0c8a..000000000 --- a/_preview/419/_sources/core/numpy.md +++ /dev/null @@ -1,13 +0,0 @@ -NumPy Logo - -# NumPy - -This section contains tutorials on array computing with [NumPy](https://numpy.org). - ---- - -From the [NumPy documentation](https://numpy.org/doc/stable/user/whatisnumpy.html): - -> NumPy is the fundamental package for scientific computing in Python. It is a Python library that provides a multidimensional array object, various derived objects (such as masked arrays and matrices), and an assortment of routines for fast operations on arrays, including mathematical, logical, shape manipulation, sorting, selecting, I/O, discrete Fourier transforms, basic linear algebra, basic statistical operations, random simulation, and much more. - -NumPy's position at the center of the scientific Python ecosystem means that all users should start here in their learning journey through the core scientific packages. diff --git a/_preview/419/_sources/core/numpy/intermediate-numpy.ipynb b/_preview/419/_sources/core/numpy/intermediate-numpy.ipynb deleted file mode 100644 index 0a660cf22..000000000 --- a/_preview/419/_sources/core/numpy/intermediate-numpy.ipynb +++ /dev/null @@ -1,676 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"NumPy\n", - "# Intermediate NumPy\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "1. Working with multiple dimensions\n", - "1. Subsetting of irregular arrays with booleans\n", - "1. Sorting, or indexing with indices" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [NumPy Basics](numpy-basics) | Necessary | |\n", - "\n", - "* **Time to learn**: 20 minutes\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "We will be including [Matplotlib](../matplotlib) to illustrate some of our examples, but you don't need knowledge of it to complete this notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using axes to slice arrays\n", - "\n", - "Here we introduce an important concept when working with NumPy: the axis. This indicates the particular dimension along which a function should operate (provided the function does something taking multiple values and converts to a single value). \n", - "\n", - "Let's look at a concrete example with `sum`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.arange(12).reshape(3, 4)\n", - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This calculates the total of all values in the array." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.sum(a)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Some of NumPy's functions can be accessed as `ndarray` methods!\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.sum()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, with a reminder about how our array is shaped," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "we can specify `axis` to get _just_ the sum across each of our rows." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.sum(a, axis=0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or do the same and take the sum across columns:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.sum(a, axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After putting together some data and introducing some more advanced calculations, let's demonstrate a multi-layered example: calculating temperature advection. If you're not familiar with this (don't worry!), we'll be looking to calculate\n", - "\n", - "\\begin{equation*}\n", - "\\text{advection} = -\\vec{v} \\cdot \\nabla T\n", - "\\end{equation*}\n", - "\n", - "and to do so we'll start with some random $T$ and $\\vec{v}$ values," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp = np.random.randn(100, 50)\n", - "u = np.random.randn(100, 50)\n", - "v = np.random.randn(100, 50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can calculate the `np.gradient` of our new $T(100x50)$ field as two separate component gradients," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gradient_x, gradient_y = np.gradient(temp)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In order to calculate $-\\vec{v} \\cdot \\nabla T$, we will use `np.dstack` to turn our two separate component gradient fields into one multidimensional field containing $x$ and $y$ gradients at each of our $100x50$ points," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grad_vectors = np.dstack([gradient_x, gradient_y])\n", - "print(grad_vectors.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and then do the same for our separate $u$ and $v$ wind components," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wind_vectors = np.dstack([u, v])\n", - "print(wind_vectors.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we can calculate the dot product of these two multidimensional fields of wind and temperature gradient components by hand as an element-wise multiplication, `*`, and then a `sum` of our separate components at each point (i.e., along the last `axis`)," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "advection = (wind_vectors * -grad_vectors).sum(axis=-1)\n", - "print(advection.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Indexing arrays with boolean values\n", - "\n", - "### Array comparisons\n", - "NumPy can easily create arrays of boolean values and use those to select certain values to extract from an array" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create some synthetic data representing temperature and wind speed data\n", - "np.random.seed(19990503) # Make sure we all have the same data\n", - "temp = 20 * np.cos(np.linspace(0, 2 * np.pi, 100)) + 50 + 2 * np.random.randn(100)\n", - "speed = np.abs(\n", - " 10 * np.sin(np.linspace(0, 2 * np.pi, 100)) + 10 + 5 * np.random.randn(100)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(temp, 'tab:red')\n", - "plt.plot(speed, 'tab:blue');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By doing a comparison between a NumPy array and a value, we get an\n", - "array of values representing the results of the comparison between\n", - "each element and the value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp > 45" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This, which is its own NumPy array of `boolean` values, can be used as an index to another array of the same size. We can even use it as an index within the original `temp` array we used to compare," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[temp > 45]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " This only returns the values from our original array meeting the indexing conditions, nothing more! Note the size,\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[temp > 45].shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Warning

\n", - " Indexing arrays with arrays requires them to be the same size!\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we store this array somewhere new," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp_45 = temp[temp > 45]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [], - "source": [ - "temp_45[temp < 45]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We find that our original `(100,)` shape array is too large to subset our new `(60,)` array." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If their sizes _do_ match, the boolean array can come from a totally different array!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "speed > 10" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[speed > 10]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Replacing values\n", - "To extend this, we can use this conditional indexing to _assign_ new values to certain positions within our array, somewhat like a masking operation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Make a copy so we don't modify the original data\n", - "temp2 = temp.copy()\n", - "speed2 = speed.copy()\n", - "\n", - "# Replace all places where speed is <10 with NaN (not a number)\n", - "temp2[speed < 10] = np.nan\n", - "speed2[speed < 10] = np.nan" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(temp2, 'tab:red');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and to put this in context," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(temp, 'r:')\n", - "plt.plot(temp2, 'r')\n", - "plt.plot(speed, 'b:')\n", - "plt.plot(speed2, 'b');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we use parentheses to preserve the order of operations, we can combine these conditions with other bitwise operators like the `&` for `bitwise_and`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "multi_mask = (temp < 45) & (speed > 10)\n", - "multi_mask" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[multi_mask]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Heat index is only defined for temperatures >= 80F and relative humidity values >= 40%. Using the data generated below, we can use boolean indexing to extract the data where heat index has a valid value." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Here's the \"data\"\n", - "np.random.seed(19990503)\n", - "temp = 20 * np.cos(np.linspace(0, 2 * np.pi, 100)) + 80 + 2 * np.random.randn(100)\n", - "relative_humidity = np.abs(\n", - " 20 * np.cos(np.linspace(0, 4 * np.pi, 100)) + 50 + 5 * np.random.randn(100)\n", - ")\n", - "\n", - "# Create a mask for the two conditions described above\n", - "good_heat_index = (temp >= 80) & (relative_humidity >= 0.4)\n", - "\n", - "# Use this mask to grab the temperature and relative humidity values that together\n", - "# will give good heat index values\n", - "print(temp[good_heat_index])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Another bitwise operator we can find helpful is Python's `~` complement operator, which can give us the **inverse** of our specific mask to let us assign `np.nan` to every value _not_ satisfied in `good_heat_index`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_temp = temp.copy()\n", - "plot_temp[~good_heat_index] = np.nan\n", - "plt.plot(plot_temp, 'tab:red');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Indexing using arrays of indices\n", - "\n", - "You can also use a list or array of indices to extract particular values--this is a natural extension of the regular indexing. For instance, just as we can select the first element:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also extract the first, fifth, and tenth elements as a list:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[[0, 4, 9]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "One of the ways this comes into play is trying to sort NumPy arrays using `argsort`. This function returns the indices of the array that give the items in sorted order. So for our `temp`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inds = np.argsort(temp)\n", - "inds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "i.e., our lowest value is at index `52`, next `57`, and so on. We can use this array of indices as an index for `temp`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp[inds]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "to get a sorted array back!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With some clever slicing, we can pull out the last 10, or 10 highest, values of `temp`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ten_highest = inds[-10:]\n", - "print(temp[ten_highest])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are other NumPy `arg` functions that return indices for operating; check out the [NumPy docs](https://numpy.org/doc/stable/reference/routines.sort.html) on sorting your arrays!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "In this notebook we introduced the power of understanding the dimensions of our data by specifying math along `axis`, used `True` and `False` values to subset our data according to conditions, and used lists of positions within our array to sort our data.\n", - "\n", - "### What's Next\n", - "Taking some time to practice this is valuable to be able to quickly manipulate arrays of information in useful or scientific ways." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and references\n", - "The [NumPy Users Guide](https://numpy.org/devdocs/user/quickstart.html#less-basic) expands further on some of these topics, as well as suggests various [Tutorials](https://numpy.org/learn/), lectures, and more at this stage." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.2" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/numpy/numpy-basics.ipynb b/_preview/419/_sources/core/numpy/numpy-basics.ipynb deleted file mode 100644 index 2713c3115..000000000 --- a/_preview/419/_sources/core/numpy/numpy-basics.ipynb +++ /dev/null @@ -1,1034 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"NumPy\n", - "# NumPy Basics\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "NumPy is the fundamental package for scientific computing with Python. It contains among other things:\n", - "\n", - "- a powerful N-dimensional array object\n", - "- sophisticated (broadcasting) functions\n", - "- useful linear algebra, Fourier transform, and random number capabilities\n", - "\n", - "The NumPy array object is the common interface for working with typed arrays of data across a wide-variety of scientific Python packages. NumPy also features a C-API, which enables interfacing existing Fortran/C/C++ libraries with Python and NumPy. In this notebook we will cover\n", - "\n", - "1. Creating an `array`\n", - "1. Math and calculations with arrays\n", - "1. Inspecting an array with slicing and indexing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Python Quickstart](../../foundations/quickstart) | Necessary | Lists, indexing, slicing, math |\n", - "\n", - "* **Time to learn**: 35 minutes\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "A common convention you might encounter is to rename `numpy` to `np` on import to shorten it for the many times we will be calling on `numpy` for functionality." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create an array of 'data'\n", - "\n", - "The NumPy array represents a *contiguous* block of memory, holding entries of a given type (and hence fixed size). The entries are laid out in memory according to the shape, or list of dimension sizes. Let's start by creating an array from a list of integers and taking a look at it," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.array([1, 2, 3])\n", - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can inspect the number of dimensions our array is organized along with `ndim`, and how long each of these dimensions are with `shape`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.ndim" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So our 1-dimensional array has a shape of `3` along that dimension! Finally we can check out the underlying type of our underlying data," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.dtype" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's expand this with a new data type, and by using a list of lists we can grow the dimensions of our array!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])\n", - "a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.ndim" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.dtype" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And as before we can use `ndim`, `shape`, and `dtype` to discover how many dimensions of what lengths are making up our array of floats." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Generation\n", - "NumPy also provides helper functions for generating arrays of data to save you typing for regularly spaced data. Don't forget your Python indexing rules!\n", - "\n", - "* `arange(start, stop, step)` creates a range of values in the interval `[start,stop)` with `step` spacing.\n", - "* `linspace(start, stop, num)` creates a range of `num` evenly spaced values over the range `[start,stop]`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### arange" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.arange(5)\n", - "a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.arange(3, 11)\n", - "a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.arange(1, 10, 2)\n", - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### linspace" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.linspace(0, 4, 5)\n", - "b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.linspace(3, 10, 15)\n", - "b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.linspace(2.5, 10.25, 11)\n", - "b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.linspace(0, 100, 30)\n", - "b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Perform calculations with NumPy\n", - "\n", - "### Arithmetic\n", - "\n", - "In core Python, that is *without* NumPy, creating sequences of values and adding them together requires writing a lot of manual loops, just like one would do in C/C++:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = list(range(5, 10))\n", - "b = [3 + i * 1.5 / 4 for i in range(5)]\n", - "\n", - "a, b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result = []\n", - "for x, y in zip(a, b):\n", - " result.append(x + y)\n", - "print(result)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "That is very verbose and not very intuitive. Using NumPy this becomes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.arange(5, 10)\n", - "b = np.linspace(3, 4.5, 5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a + b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Many major mathematical operations operate in the same way. They perform an element-by-element calculation of the two arrays." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a - b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a / b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a**b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Warning

\n", - " These arrays must be the same shape!\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.linspace(3, 4.5, 6)\n", - "a.shape, b.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [], - "source": [ - "a * b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Constants\n", - "\n", - "NumPy provides us access to some useful constants as well - remember you should never be typing these in manually! Other libraries such as SciPy and MetPy have their own set of constants that are more domain specific." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.pi" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.e" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can use these for classic calculations you might be familiar with! Here we can create a range `t = [0, 2 pi]` by `pi/4`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "t = np.arange(0, 2 * np.pi + np.pi / 4, np.pi / 4)\n", - "t" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "t / np.pi" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Array math functions\n", - "\n", - "NumPy also has math functions that can operate on arrays. Similar to the math operations, these greatly simplify and speed up these operations. Let's start with calculating $\\sin(t)$!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sin_t = np.sin(t)\n", - "sin_t" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and clean it up a bit by `round`ing to three decimal places." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.round(sin_t, 3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cos_t = np.cos(t)\n", - "cos_t" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Check out NumPy's list of mathematical functions here!\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can convert between degrees and radians with only NumPy, by hand" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "t / np.pi * 180" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or with built-in function `rad2deg`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "degrees = np.rad2deg(t)\n", - "degrees" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are similarly provided algorithms for operations including integration, bulk summing, and cumulative summing." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sine_integral = np.trapz(sin_t, t)\n", - "np.round(sine_integral, 3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cos_sum = np.sum(cos_t)\n", - "cos_sum" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cos_csum = np.cumsum(cos_t)\n", - "print(cos_csum)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Indexing and subsetting arrays\n", - "\n", - "### Indexing\n", - "\n", - "We can use integer indexing to reach into our arrays and pull out individual elements. Let's make a toy 2-d array to explore. Here we create a 12-value `arange` and `reshape` it into a 3x4 array." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.arange(12).reshape(3, 4)\n", - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Recall that Python indexing starts at `0`, and we can begin indexing our array with the list-style `list[element]` notation," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "to pull out just our first _row_ of data within `a`. Similarly we can index in reverse with negative indices," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "to pull out just the last row of data within `a`. This notation extends to as many dimensions as make up our array as `array[m, n, p, ...]`. The following diagram shows these indices for an example, 2-dimensional `6x6` array," - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![](array_index.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For example, let's find the entry in our array corresponding to the 2nd row (`m=1` in Python) and the 3rd column (`n=2` in Python)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[1, 2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can again use these negative indices to index backwards," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[-1, -1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and even mix-and-match along dimensions," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[1, -2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Slices\n", - "\n", - "Slicing syntax is written as `array[start:stop[:step]]`, where **all numbers are optional**.\n", - "- defaults: \n", - " - start = 0\n", - " - stop = len(dim)\n", - " - step = 1\n", - "- The second colon is **also optional** if no step is used.\n", - "\n", - "Let's pull out just the first row, `m=0` of `a` and see how this works!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = a[0]\n", - "b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Laying out our default slice to see the entire array explicitly looks something like this," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[0:4:1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where again, these default values are optional," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[::]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and even the second `:` is optional" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[:]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now to actually make our own slice, let's select all elements from `m=0` to `m=2`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[0:2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Warning

\n", - " Slice notation is exclusive of the final index.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This means that slices will include every value **up to** your `stop` index and not this index itself, like a half-open interval `[start, end)`. For example," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[3]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "reveals a different value than" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[0:3]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, a few more examples of this notation before reintroducing our 2-d array `a`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[2:] # m=2 through the end, can leave off the number" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b[:3] # similarly, the same as our b[0:3]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Multidimensional slicing\n", - "This entire syntax can be extended to each dimension of multidimensional arrays." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First let's pull out rows `0` through `2`, and then every `:` column for each of those" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[0:2, :]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly, let's get all rows for just column `2`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[:, 2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or just take a look at the full row `:`, for every second column, `::2`," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[:, ::2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For any shape of array, you can use `...` to capture full slices of every non-specified dimension. Consider the 3-D array," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "c = a.reshape(2, 2, 3)\n", - "c" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "c[0, ...]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and so this is equivalent to" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "c[0, :, :]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "for extracting every dimension across our first row. We can also flip this around," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "c[..., -1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "to investigate every preceding dimension along our the last entry of our last axis, the same as `c[:, :, -1]`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "In this notebook we introduced NumPy and the `ndarray` that is so crucial to the entirety of the scientific Python community ecosystem. We created some arrays, used some of NumPy's own mathematical functions to manipulate them, and then introduced the world of NumPy indexing and selecting for even multi-dimensional arrays.\n", - "\n", - "### What's next?\n", - "This notebook is the gateway to nearly every other Pythia resource here. This information is crucial for understanding SciPy, pandas, xarray, and more. Continue into NumPy to explore some more intermediate and advanced topics!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and references\n", - "- [NumPy User Guide](http://docs.scipy.org/doc/numpy/user/)\n", - "- [SciPy Lecture Notes](https://scipy-lectures.org/)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.2" - }, - "toc-autonumbering": false - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/numpy/numpy-broadcasting.ipynb b/_preview/419/_sources/core/numpy/numpy-broadcasting.ipynb deleted file mode 100644 index ee6bee2ac..000000000 --- a/_preview/419/_sources/core/numpy/numpy-broadcasting.ipynb +++ /dev/null @@ -1,939 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"NumPy\n", - "# NumPy Broadcasting\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "Before we begin, it is important to know that broadcasting is a valuable part of the power that NumPy provides. However, there's no looking past the fact that broadcasting can be conceptually difficult to digest. This information can be helpful and very powerful, but it may be more prudent to first start learning the other label-based elements of the Python ecosystem, [Pandas](../pandas) and [Xarray](../xarray). This can make understanding NumPy broadcasting easier or simpler when using real-world data. When you are ready to learn about NumPy broadcasting, this section is organized as follows:\n", - "\n", - "1. An introduction to broadcasting\n", - "1. Avoiding loops with vectorization" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [NumPy Basics](numpy-basics) | Necessary | |\n", - "| [Intermediate NumPy](intermediate-numpy) | Helpful | |\n", - "| [Conceptual guide to broadcasting](https://numpy.org/doc/stable/user/theory.broadcasting.html#array-broadcasting-in-numpy) | Helpful | |\n", - "\n", - "* **Time to learn**: 30 minutes\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports\n", - "\n", - "As always, when working with NumPy, it must be imported first:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using broadcasting to implicitly loop over data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### What is broadcasting?\n", - "Broadcasting is a useful NumPy tool that allows us to perform operations between arrays with different shapes, provided that they are compatible with each other in certain ways. To start, we can create an array below and add 5 to it:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.array([10, 20, 30, 40])\n", - "a + 5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This works even though 5 is not an array. It behaves as expected, adding 5 to each of the elements in `a`. This also works if 5 is an array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.array([5])\n", - "a + b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This takes the single element in `b` and adds it to each of the elements in `a`. This won't work for just any `b`, though; for instance, the following won't work:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [], - "source": [ - "b = np.array([5, 6, 7])\n", - "a + b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It does work if `a` and `b` are the same shape:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.array([5, 5, 10, 10])\n", - "a + b" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What if what we really want is pairwise addition of a and b? Without broadcasting, we could accomplish this by looping:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b = np.array([1, 2, 3, 4, 5])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result = np.empty((5, 4), dtype=np.int32)\n", - "for row, valb in enumerate(b):\n", - " for col, vala in enumerate(a):\n", - " result[row, col] = vala + valb\n", - "result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also do this by manually repeating the arrays to the proper shape for the result, using `np.tile`. This avoids the need to manually loop:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aa = np.tile(a, (5, 1))\n", - "aa" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Turn b into a column array, then tile it\n", - "bb = np.tile(b.reshape(5, 1), (1, 4))\n", - "bb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aa + bb" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Giving NumPy room for broadcasting\n", - "We can also do this using broadcasting, which is where NumPy implicitly repeats the array without using additional memory. With broadcasting, NumPy takes care of repeating for you, provided dimensions are \"compatible\". This works as follows:\n", - "1. Check the number of dimensions of the arrays. If they are different, *prepend* dimensions of size one until the arrays are the same dimension shape.\n", - "2. Check if each of the dimensions are compatible. This works as follows:\n", - " - Each dimension is checked.\n", - " - If one of the arrays has a size of 1 in the checked dimension, or both arrays have the same size in the checked dimension, the check passes.\n", - " - If all dimension checks pass, the dimensions are compatible.\n", - "\n", - "For example, consider the following arrays:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "b.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Right now, these arrays both have the same number of dimensions. They both have only one dimension, but that dimension is incompatible. We can solve this by appending a dimension using `np.newaxis` when indexing, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bb = b[:, np.newaxis]\n", - "bb.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a + bb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "(a + bb).shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also make the code more succinct by performing the newaxis and addition operations in a single line, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a + b[:, np.newaxis]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Extending to higher dimensions\n", - "The same broadcasting ability and rules also apply for arrays of higher dimensions. Consider the following arrays `x`, `y`, and `z`, which are all different dimensions. We can use newaxis and broadcasting to perform $x^2 + y^2 + z^2$:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = np.array([1, 2])\n", - "y = np.array([3, 4, 5])\n", - "z = np.array([6, 7, 8, 9])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we extend the `x` array using newaxis, and then square it. Then, we square `y`, and broadcast it onto the extended `x` array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "d_2d = x[:, np.newaxis] ** 2 + y**2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "d_2d.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we further extend this new 2-D array to a 3-D array using newaxis, square the `z` array, and then broadcast `z` onto the newly extended array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "d_3d = d_2d[..., np.newaxis] + z**2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "d_3d.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As described above, we can also perform these operations in a single line of code, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "h = x[:, np.newaxis, np.newaxis] ** 2 + y[np.newaxis, :, np.newaxis] ** 2 + z**2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use the shape method to see the shape of the array created by the single line of code above. As you can see, it matches the shape of the array created by the multi-line process above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "h.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also use the all method to confirm that both arrays contain the same data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.all(h == d_3d)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Broadcasting is often useful when you want to do calculations with coordinate values, which are often given as 1-D arrays corresponding to positions along a particular array dimension. For example, we can use broadcasting to help with taking range and azimuth values for radar data (1-D separable polar coordinates) and converting to x,y pairs relative to the radar location." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Given the 3-D temperature field and 1-D pressure coordinates below, let's calculate $T * exp(P / 1000)$. We will need to use broadcasting to make the arrays compatible. The following code demonstrates how to use newaxis and broadcasting to perform this calculation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pressure = np.array([1000, 850, 500, 300])\n", - "temps = np.linspace(20, 30, 24).reshape(4, 3, 2)\n", - "pressure.shape, temps.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pressure[:, np.newaxis, np.newaxis].shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps * np.exp(pressure[:, np.newaxis, np.newaxis] / 1000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Vectorize calculations to avoid explicit loops" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When working with arrays of data, loops over the individual array elements is a fact of life. However, for improved runtime performance, it is important to avoid performing these loops in Python as much as possible, and let NumPy handle the looping for you. Avoiding these loops frequently, but not always, results in shorter and clearer code as well." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Look ahead/behind\n", - "\n", - "One common pattern for vectorizing is in converting loops that work over the current point, in addition to the previous point and/or the next point. This comes up when doing finite-difference calculations, e.g., approximating derivatives:\n", - "\n", - "\\begin{equation*}\n", - "f'(x) = f_{i+1} - f_{i}\n", - "\\end{equation*}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a = np.linspace(0, 20, 6)\n", - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can calculate the forward difference for this array using a manual loop, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "d = np.zeros(a.size - 1)\n", - "for i in range(len(a) - 1):\n", - " d[i] = a[i + 1] - a[i]\n", - "d" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It would be nice to express this calculation without a loop, if possible. To see how to go about this, let's consider the values that are involved in calculating `d[i]`; in other words, the values `a[i+1]` and `a[i]`. The values over the loop iterations are:\n", - "\n", - "| i | a[i+1] | a[i] |\n", - "| --- | ---- | ---- |\n", - "| 0 | 4 | 0 |\n", - "| 1 | 8 | 4 |\n", - "| 2 | 12 | 8 |\n", - "| 3 | 16 | 12 |\n", - "| 4 | 20 | 16 |\n", - "\n", - "We can then express the series of values for `a[i+1]` as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[1:]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also express the series of values for `a[i]` as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[:-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This means that we can express the forward difference using the following statement:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a[1:] - a[:-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It should be noted that using slices in this way returns only a **view** on the original array. In other words, you can use the slices to modify the original data, either intentionally or accidentally. Also, this is a quick operation that does not involve a copy and does not bloat memory usage." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 2nd Derivative\n", - " \n", - "A finite-difference estimate of the 2nd derivative is given by the following equation (ignoring $\\Delta x$):\n", - "\n", - "\\begin{equation*}\n", - "f''(x) = 2\n", - "f_i - f_{i+1} - f_{i-1}\n", - "\\end{equation*}\n", - "\n", - "Let's write some vectorized code to calculate this finite difference for `a`, using slices. Analyze the code below, and compare the result to the values you would expect to see from the 2nd derivative of `a`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "2 * a[1:-1] - a[:-2] - a[2:]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Blocking\n", - "\n", - "Another application that can become more efficient using vectorization is operating on blocks of data. Let's start by creating some temperature data (rounding to make it easier to see and recognize the values):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps = np.round(20 + np.random.randn(10) * 5, 1)\n", - "temps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's start by writing a loop to take a 3-point running mean of the data. We'll do this by iterating over all points in the array and averaging the 3 points centered on each point. We'll simplify the problem by avoiding dealing with the cases at the edges of the array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "avg = np.zeros_like(temps)\n", - "for i in range(1, len(temps) - 1):\n", - " sub = temps[i - 1 : i + 2]\n", - " avg[i] = sub.mean()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "avg" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As with the case of doing finite differences, we can express this using slices of the original array instead of loops:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# i - 1 i i + 1\n", - "(temps[:-2] + temps[1:-1] + temps[2:]) / 3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Another option to solve this type of problem is to use the powerful NumPy tool `as_strided` instead of slicing. This tool can result in some odd behavior, so take care when using it. However, the trade-off is that the `as_strided` tool can be used to perform powerful operations. What we're doing here is altering how NumPy is interpreting the values in the memory that underpins the array. Take this array, for example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using `as_strided`, we can create a view of this array with a new, bigger shape, with rows made up of overlapping values. We do this by specifying a new shape of 8x3. There are 3 columns, for fitting blocks of data containing 3 values each, and 8 rows, to correspond to the 8 blocks of data of that size that are possible in the original 1-D array. We can then use the `strides` argument to control how NumPy walks between items in each dimension. The last item in the strides tuple simply states that the number of bytes to walk between items is just the size of an item. (Increasing this last item would skip items.) The first item says that when we go to a new element (in this example, a new row), only advance the size of a single item. This is what gives us overlapping rows. The code for these operations looks like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "block_size = 3\n", - "new_shape = (len(temps) - block_size + 1, block_size)\n", - "bytes_per_item = temps.dtype.itemsize\n", - "temps_strided = np.lib.stride_tricks.as_strided(\n", - " temps, shape=new_shape, strides=(bytes_per_item, bytes_per_item)\n", - ")\n", - "temps_strided" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we have this view of the array with the rows representing overlapping blocks, we can operate across the rows with `mean` and the `axis=-1` argument to get our running average:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_strided.mean(axis=-1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It should be noted that there are no copies going on here, so if we change a value at a single indexed location, the change actually shows up in multiple locations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps_strided[0, 2] = 2000\n", - "temps_strided" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Finding the difference between min and max\n", - "\n", - "Another operation that crops up when slicing and dicing data is trying to identify a set of indices along a particular axis, contained within a larger multidimensional array. For instance, say we have a 3-D array of temperatures, and we want to identify the location of the $-10^oC$ isotherm within each column:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pressure = np.linspace(1000, 100, 25)\n", - "temps = np.random.randn(25, 30, 40) * 3 + np.linspace(25, -100, 25).reshape(-1, 1, 1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "NumPy has the function `argmin()`, which returns the index of the minimum value. We can use this to find the minimum absolute difference between the value and -10:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Using axis=0 to tell it to operate along the pressure dimension\n", - "inds = np.argmin(np.abs(temps - -10), axis=0)\n", - "inds" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inds.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Great! We now have an array representing the index of the point closest to $-10^oC$ in each column of data. We can use this new array as a lookup index for our pressure coordinate array to find the pressure level for each column:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pressure[inds]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can try to find the closest actual temperature value using the new array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps[inds, :, :].shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Unfortunately, this replaced the pressure dimension (size 25) with the shape of our index array (30 x 40), giving us a 30 x 40 x 30 x 40 array. Obviously, if scientifically relevant data values were being used, this result would almost certainly make such data invalid. One solution would be to set up a loop with the `ndenumerate` function, like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "output = np.empty(inds.shape, dtype=temps.dtype)\n", - "for (i, j), val in np.ndenumerate(inds):\n", - " output[i, j] = temps[val, i, j]\n", - "output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Of course, what we really want to do is avoid the explicit loop. Let's temporarily simplify the problem to a single dimension. If we have a 1-D array, we can pass a 1-D array of indices (a full range), and get back the same as the original data array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pressure[np.arange(pressure.size)]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.all(pressure[np.arange(pressure.size)] == pressure)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use this to select all the indices on the other dimensions of our temperature array. We will also need to use the magic of broadcasting to combine arrays of indices across dimensions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This can be written as a vectorized solution. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_inds = np.arange(temps.shape[1])[:, np.newaxis]\n", - "x_inds = np.arange(temps.shape[2])\n", - "temps[inds, y_inds, x_inds]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can use this new array to find, for example, the relative humidity at the $-10^oC$ isotherm:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "np.all(output == temps[inds, y_inds, x_inds])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "We've previewed some advanced NumPy capabilities, with a focus on _vectorization_; in other words, using clever broadcasting and data windowing techniques to enhance the speed and readability of our calculation code. By making use of vectorization, you can reduce explicit construction of loops in your code, and improve speed of calculation throughout the execution of such code.\n", - "\n", - "### What's next\n", - "This is an advanced NumPy topic; however, it is important to learn this topic in order to design calculation code that maximizes scalability and speed. If you would like to explore this topic further, please review the links below. We also suggest diving into label-based indexing and subsetting with [Pandas](../pandas) and [Xarray](../xarray), where some of this broadcasting can be simplified, or have added context." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and references\n", - "* [NumPy Broadcasting Documentation](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/overview.md b/_preview/419/_sources/core/overview.md deleted file mode 100644 index 27519db4a..000000000 --- a/_preview/419/_sources/core/overview.md +++ /dev/null @@ -1,125 +0,0 @@ -# Overview - -A group of programs that works in tandem to produce a result or achieve a common goal is often referred to as a software stack. -This page gives an overview of the Python geoscience stack. -Scroll to the end of the page for cross-referenced tutorial material for several of the packages in the stack. -We suggest that new users start with the [Foundational Skills](../foundations/overview) section in order to get the most out of these tutorials. - -## Core libraries - -Most geoscience data analysis involves working with numerical arrays. -The default library for dealing with numerical arrays in Python is [NumPy](http://www.numpy.org/). -It has some built in functions for calculating very simple statistics -(e.g. maximum, mean, standard deviation), -but for more complex analysis -(e.g. interpolation, integration, linear algebra) -the [SciPy](https://scipy.org) library is the default. -If you’re dealing with particularly large arrays, -[Dask](https://dask.org/) works with the existing Python ecosystem -(including NumPy) to scale your analysis -to multi-core machines and/or distributed clusters (i.e. parallel processing). - -Another common feature of geo-data science is time series analysis. -The Python standard library comes with a [datetime](https://docs.python.org/3/library/datetime.html) -package for manipulating dates and times. -NumPy also includes a [datetime64](https://numpy.org/doc/stable/reference/arrays.datetime.html) -module for efficient vectorized datetime operations -and the [cftime](https://unidata.github.io/cftime/) library -is useful for dealing with non-standard calendars. - -When it comes to data visualization, -the default library is [Matplotlib](https://matplotlib.org/). -As you can see at the [Matplotlib gallery](https://matplotlib.org/stable/gallery/index.html), -this library is great for any simple (e.g. bar charts, contour plots, line graphs), -static (e.g. .png, .eps, .pdf) plots. -The [Cartopy](https://scitools.org.uk/cartopy/docs/latest/) library -provides additional plotting functionality for common geographic map projections. - -## High-level libraries - -While pretty much all data analysis and visualization tasks -could be achieved with a combination of the core libraries, -their flexible, all-purpose nature means relatively common/simple tasks -can often require quite a bit of work (i.e. many lines of code). -To make things more efficient for data scientists, -the scientific Python community has therefore built a number of libraries on top of the core stack. -These high-levels libraries aren’t as flexible -– they can’t do _everything_ like the core stack can – -but they can do common tasks with far less effort. - -The most popular high-level data science library is undoubtedly [Pandas](http://pandas.pydata.org/). -The key advance offered by Pandas is the concept of labeled arrays. -Rather than referring to the individual elements of a data array using a numeric index -(as is required with NumPy), -the actual row and column headings can be used. -That means information from the cardiac ward on 3 July 2005 -could be obtained from a medical dataset by asking for `data['cardiac'].loc['2005-07-03']`, -rather than having to remember the numeric index corresponding to that ward and date. -This labeled array feature, -combined with a bunch of other features that streamline common statistical and plotting tasks -traditionally performed with SciPy, datetime and Matplotlib, -greatly simplifies the code development process (read: less lines of code). - -One of the limitations of Pandas -is that it’s only able to handle one- or two-dimensional (i.e. tabular) data arrays. -The [Xarray](http://xarray.pydata.org/) library was therefore created -to extend the labelled array concept to x-dimensional arrays. -Not all of the Pandas functionality is available -(which is a trade-off associated with being able to handle multi-dimensional arrays), -but the ability to refer to array elements by their actual latitude (e.g. 20 South), -longitude (e.g. 50 East), height (e.g. 500 hPa) and time (e.g. 2015-04-27), for example, -makes the Xarray data array far easier to deal with than the NumPy array. -As an added bonus, -Xarray also has built in functionality for reading/writing specific geoscience file formats -(e.g netCDF, GRIB) -and incorporates Dask under the hood to make dealing with large arrays easier. - -You will occasionally find yourself needing to use a core library directly -(e.g. you might create a plot with Xarray and then call a specific Matplotlib -function to customise a label on that plot), -but to avoid re-inventing the wheel your first impulse should always be -to check whether a high-level library like Pandas or Xarray has the functionality you need. -Nothing would be more heartbreaking than spending hours writing your own function -using the netCDF4 library for extracting the metadata contained within a netCDF file, -for instance, -only to find that Xarray automatically keeps this information upon reading a netCDF file. -In this way, a solid working knowledge of the geoscience stack -can save you a lot of time and effort. - -## Domain-specific libraries - -So far we’ve considered libraries that do general, -broad-scale tasks like data input/output, common statistics, visualisation, etc. -Given their large user base, -these libraries are usually written and supported by large companies/institutions -(e.g. the MetOffice supports Cartopy) -or the wider PyData community (e.g. NumPy, Pandas, Xarray). -Within each sub-discipline of the geosciences, -individuals and research groups take these general libraries -and apply them to their very specific data analysis tasks. -Increasingly, these individuals and groups -are formally packaging and releasing their code for use within their community. -For instance, Andrew Dawson (an atmospheric scientist at Oxford) -does a lot of EOF analysis and manipulation of wind data, -so he has released his [eofs](https://ajdawson.github.io/eofs/latest/) -and [windspharm](https://ajdawson.github.io/windspharm/latest/) libraries -(which are able to handle data arrays from NumPy or Xarray). -Similarly, a group at the Atmospheric Radiation Measurement (ARM) Climate Research Facility -have released their Python ARM Radar Toolkit ([Py-ART](http://arm-doe.github.io/pyart/)) -for analysing weather radar data. - -There are too many domain specific libraries to mention here, -but online resources such as the -[Python for Atmosphere and Ocean Science (PyAOS) package index](https://pyaos.github.io/packages/) -attempt to keep track of the domain-specific libraries in their field. -Also check out the [Pythia Resource Gallery](https://projectpythia.org/resource-gallery.html) and try filtering by domain. - -## Tutorials - -- [NumPy](numpy): Core package for array computing, the workhorse of the Scientific Python stack -- [Matplotlib](matplotlib): Basic plotting -- [Cartopy](cartopy): Plotting on map projections -- [Datetime](datetime): Dealing with time and calendar data -- [Pandas](pandas): Working with labeled tabular data -- [Data formats](data-formats): Working with common geoscience data formats -- [Xarray](xarray): Working with gridded and labeled N-dimensional data diff --git a/_preview/419/_sources/core/pandas.md b/_preview/419/_sources/core/pandas.md deleted file mode 100644 index 680210b1b..000000000 --- a/_preview/419/_sources/core/pandas.md +++ /dev/null @@ -1,15 +0,0 @@ -# Pandas - -```{note} -This content is under construction! -``` - -This section will contain tutorials on using [pandas](https://pandas.pydata.org) for labeled tabular data. - ---- - -From the [official documentation](https://pandas.pydata.org/), Pandas "is a fast, powerful, flexible and easy to use open source data analysis and manipulation tool, built on top of the Python programming language." - -Pandas is a very powerful library for working with tabular data (e.g., spreadsheets, comma-separated-value files, or database printouts; all of these are quite common for geoscientific data). It allows us to use labels for our data; this, in turn, allows us to write expressive and robust code to manipulate the data. - -Key features of Pandas are the abilities to read in tabular data and to slice and dice data, as well as exploratory analysis tools native to the library. diff --git a/_preview/419/_sources/core/pandas/pandas.ipynb b/_preview/419/_sources/core/pandas/pandas.ipynb deleted file mode 100644 index 21843074a..000000000 --- a/_preview/419/_sources/core/pandas/pandas.ipynb +++ /dev/null @@ -1,1249 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a132f14e-55b7-4894-8a09-5f08be34e4c7", - "metadata": {}, - "source": [ - "
\"pandas
\n", - "\n", - "# Introduction to Pandas\n", - "---" - ] - }, - { - "cell_type": "markdown", - "id": "b96a6aed-52ec-4d0b-bcba-5a8cb1044b5a", - "metadata": {}, - "source": [ - "## Overview\n", - "1. Introduction to pandas data structures\n", - "1. How to slice and dice pandas dataframes and dataseries\n", - "1. How to use pandas for exploratory data analysis\n", - "\n", - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Python Quickstart](../../foundations/quickstart) | Necessary | Intro to `dict` |\n", - "| [Numpy Basics](../numpy/numpy-basics) | Necessary | |\n", - "\n", - "* **Time to learn**: 60 minutes" - ] - }, - { - "cell_type": "markdown", - "id": "1c805eb8-a545-4ba2-a3bb-e8e0232da2c9", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "eb2bab73-f28a-4f78-ac03-10f46cf4e2a3", - "metadata": {}, - "source": [ - "## Imports" - ] - }, - { - "cell_type": "markdown", - "id": "b8627bb7", - "metadata": {}, - "source": [ - "You will often see the nickname `pd` used as an abbreviation for pandas in the import statement, just like `numpy` is often imported as `np`. We also import the `DATASETS` class from `pythia_datasets`, which allows us to use example datasets created for Pythia." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "daf58736", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "from pythia_datasets import DATASETS" - ] - }, - { - "cell_type": "markdown", - "id": "dab3da7f", - "metadata": {}, - "source": [ - "## The pandas [`DataFrame`](https://pandas.pydata.org/docs/user_guide/dsintro.html#dataframe)...\n", - "...is a **labeled**, two-dimensional columnar structure, similar to a table, spreadsheet, or the R `data.frame`.\n", - "\n", - "![dataframe schematic](https://github.com/pandas-dev/pandas/raw/main/doc/source/_static/schemas/01_table_dataframe.svg \"Schematic of a pandas DataFrame\")\n", - "\n", - "The `columns` that make up our `DataFrame` can be lists, dictionaries, NumPy arrays, pandas `Series`, or many other data types not mentioned here. Within these `columns`, you can have data values of many different data types used in Python and NumPy, including text, numbers, and dates/times. The first column of a `DataFrame`, shown in the image above in dark gray, is uniquely referred to as an `index`; this column contains information characterizing each row of our `DataFrame`. Similar to any other `column`, the `index` can label rows by text, numbers, datetime objects, and many other data types. Datetime objects are a quite popular way to label rows.\n", - "\n", - "For our first example using Pandas DataFrames, we start by reading in some data in comma-separated value (`.csv`) format. We retrieve this dataset from the Pythia DATASETS class (imported at the top of this page); however, the dataset was originally contained within the NCDC teleconnections database. This dataset contains many types of geoscientific data, including El Nino/Southern Oscillation indices. For more information on this dataset, review the description [here](https://www.ncdc.noaa.gov/teleconnections/enso/indicators/sst/)." - ] - }, - { - "cell_type": "markdown", - "id": "0a064237-8e78-4b57-9200-6e97987d3ad8", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " As described above, we are retrieving the datasets for these examples from Project Pythia's custom library of example data. In order to retrieve datasets from this library, you must use the statement from pythia_datasets import DATASETS. This is shown and described in the Imports section at the top of this page. The fetch() method of the DATASETS class will automatically download the data file specified as a string argument, in this case enso_data.csv, and cache the file locally, assuming the argument corresponds to a valid Pythia example dataset. This is illustrated in the following example.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0be820cd", - "metadata": {}, - "outputs": [], - "source": [ - "filepath = DATASETS.fetch('enso_data.csv')" - ] - }, - { - "cell_type": "markdown", - "id": "68316c6c", - "metadata": {}, - "source": [ - "Once we have a valid path to a data file that Pandas knows how to read, we can open it, as shown in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e99652d5", - "metadata": {}, - "outputs": [], - "source": [ - "df = pd.read_csv(filepath)" - ] - }, - { - "cell_type": "markdown", - "id": "ae9dcbbd", - "metadata": {}, - "source": [ - "If we print out our `DataFrame`, it will render as text by default, in a tabular-style ASCII output, as shown in the following example. However, if you are using a Jupyter notebook, there exists a better way to print `DataFrames`, as described below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "25a23571", - "metadata": {}, - "outputs": [], - "source": [ - "print(df)" - ] - }, - { - "cell_type": "markdown", - "id": "f22bb442", - "metadata": {}, - "source": [ - "As described above, there is a better way to print Pandas `DataFrames`. If you are using a Jupyter notebook, you can run a code cell containing the `DataFrame` object name, by itself, and it will display a nicely rendered table, as shown below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8942e69", - "metadata": {}, - "outputs": [], - "source": [ - "df" - ] - }, - { - "cell_type": "markdown", - "id": "377d4803", - "metadata": {}, - "source": [ - "The `DataFrame` index, as described above, contains information characterizing rows; each row has a unique ID value, which is displayed in the index column. By default, the IDs for rows in a `DataFrame` are represented as sequential integers, which start at 0." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cde6999b", - "metadata": {}, - "outputs": [], - "source": [ - "df.index" - ] - }, - { - "cell_type": "markdown", - "id": "8af49ac9", - "metadata": {}, - "source": [ - "At the moment, the index column of our `DataFrame` is not very helpful for humans. However, Pandas has clever ways to make index columns more human-readable. The next example demonstrates how to use optional keyword arguments to convert `DataFrame` index IDs to a human-friendly datetime format." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4657f7e", - "metadata": {}, - "outputs": [], - "source": [ - "df = pd.read_csv(filepath, index_col=0, parse_dates=True)\n", - "\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b0d3ae28", - "metadata": {}, - "outputs": [], - "source": [ - "df.index" - ] - }, - { - "cell_type": "markdown", - "id": "8d26ed71", - "metadata": {}, - "source": [ - "Each of our data rows is now helpfully labeled by a datetime-object-like index value; this means that we can now easily identify data values not only by named columns, but also by date labels on rows. This is a sneak preview of the `DatetimeIndex` functionality of Pandas; this functionality enables a large portion of Pandas' timeseries-related usage. Don't worry; `DatetimeIndex` will be discussed in full detail later on this page. In the meantime, let's look at the columns of data read in from the `.csv` file:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "847347f8", - "metadata": {}, - "outputs": [], - "source": [ - "df.columns" - ] - }, - { - "cell_type": "markdown", - "id": "08d3b2fb", - "metadata": {}, - "source": [ - "## The pandas [`Series`](https://pandas.pydata.org/docs/user_guide/dsintro.html#series)...\n", - "\n", - "...is essentially any one of the columns of our `DataFrame`. A `Series` also includes the index column from the source `DataFrame`, in order to provide a label for each value in the `Series`.\n", - "\n", - "![pandas Series](https://github.com/pandas-dev/pandas/raw/main/doc/source/_static/schemas/01_table_series.svg \"Schematic of a pandas Series\")\n", - "\n", - "The pandas `Series` is a fast and capable 1-dimensional array of nearly any data type we could want, and it can behave very similarly to a NumPy `ndarray` or a Python `dict`. You can take a look at any of the `Series` that make up your `DataFrame`, either by using its column name and the Python `dict` notation, or by using dot-shorthand with the column name:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ee085815", - "metadata": {}, - "outputs": [], - "source": [ - "df[\"Nino34\"]" - ] - }, - { - "cell_type": "markdown", - "id": "2fcc97fa", - "metadata": {}, - "source": [ - "
\n", - "Tip: You can also use the dot notation illustrated below to specify a column name, but this syntax is mostly provided for convenience. For the most part, this notation is interchangeable with the dictionary notation; however, if the column name is not a valid Python identifier (e.g., it starts with a number or space), you cannot use dot notation.
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7d46cbb9", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34" - ] - }, - { - "cell_type": "markdown", - "id": "dfed2a7c-5532-44d4-a2be-f1cc484d842c", - "metadata": {}, - "source": [ - "## Slicing and Dicing the `DataFrame` and `Series`\n", - "\n", - "In this section, we will expand on topics covered in the previous sections on this page. One of the most important concepts to learn about Pandas is that it allows you to _**access anything by its associated label**_, regardless of data organization structure." - ] - }, - { - "cell_type": "markdown", - "id": "59128a2d-f23f-4b1c-93e7-63a85046b881", - "metadata": {}, - "source": [ - "### Indexing a `Series`\n", - "\n", - "As a review of previous examples, we'll start our next example by pulling a `Series` out of our `DataFrame` using its column label." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ec9ed333", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series = df[\"Nino34\"]\n", - "\n", - "nino34_series" - ] - }, - { - "cell_type": "markdown", - "id": "c84a81b9", - "metadata": {}, - "source": [ - "You can use syntax similar to that of NumPy `ndarrays` to index, select, and subset with Pandas `Series`, as shown in this example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "39bb0ae3", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series[3]" - ] - }, - { - "cell_type": "markdown", - "id": "a1a55a7c", - "metadata": {}, - "source": [ - "You can also use labels alongside Python dictionary syntax to perform the same operations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62006988", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series[\"1982-04-01\"]" - ] - }, - { - "cell_type": "markdown", - "id": "ce9788fd-a420-4c64-92b2-188818c52cc8", - "metadata": {}, - "source": [ - "You can probably figure out some ways to extend these indexing methods, as shown in the following examples:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "221e798d", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series[0:12]" - ] - }, - { - "cell_type": "markdown", - "id": "8ae4a117-2e37-4e01-bd06-2bef62f83741", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Index-based slices are exclusive of the final value, similar to Python's usual indexing rules.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "3cba4b8e-5dbb-4da6-ba5f-bc8a8a726b14", - "metadata": {}, - "source": [ - "However, there are many more ways to index a `Series`. The following example shows a powerful and useful indexing method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f7a06967", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series[\"1982-01-01\":\"1982-12-01\"]" - ] - }, - { - "cell_type": "markdown", - "id": "1d6b4d75-b6a5-4960-9f83-8adbff1e2830", - "metadata": {}, - "source": [ - "This is an example of label-based slicing. With label-based slicing, Pandas will automatically find a range of values based on the labels you specify." - ] - }, - { - "cell_type": "markdown", - "id": "b9c167aa-6b8d-4533-9e89-09d75af76025", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " As opposed to index-based slices, label-based slices are inclusive of the final value.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "cebfac9a-9e8f-449a-a88d-b1196a49d87d", - "metadata": {}, - "source": [ - "If you already have some knowledge of xarray, you will quite likely know how to create `slice` objects by hand. This can also be used in pandas, as shown below. If you are completely unfamiliar with xarray, it will be covered on a [later Pythia tutorial page](../xarray)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "771d6f04", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series[slice(\"1982-01-01\", \"1982-12-01\")]" - ] - }, - { - "cell_type": "markdown", - "id": "9798abf4", - "metadata": {}, - "source": [ - "### Using `.iloc` and `.loc` to index\n", - "\n", - "In this section, we introduce ways to access data that are preferred by Pandas over the methods listed above. When accessing by label, it is preferred to use the `.loc` method, and when accessing by index, the `.iloc` method is preferred. These methods behave similarly to the notation introduced above, but provide more speed, security, and rigor in your value selection. Using these methods can also help you avoid [chained assignment warnings](https://pandas.pydata.org/docs/user_guide/indexing.html#returning-a-view-versus-a-copy) generated by pandas." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5eb9de2", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series.iloc[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8d0bc3e8", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series.iloc[0:12]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "59a10070", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series.loc[\"1982-04-01\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e2b3fc1", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series.loc[\"1982-01-01\":\"1982-12-01\"]" - ] - }, - { - "cell_type": "markdown", - "id": "722e3d11-4c27-4a4c-a31b-2d551587f2b3", - "metadata": {}, - "source": [ - "### Extending to the `DataFrame`\n", - "\n", - "These subsetting capabilities can also be used in a full `DataFrame`; however, if you use the same syntax, there are issues, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8971371", - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [], - "source": [ - "df[\"1982-01-01\"]" - ] - }, - { - "cell_type": "markdown", - "id": "b89bf013-4492-461f-a1ef-a4f1a3423a4a", - "metadata": {}, - "source": [ - "
\n", - "

Danger

\n", - " Attempting to use Series subsetting with a DataFrame can crash your program. A proper way to subset a DataFrame is shown below.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "b40c7ace-939b-4997-a185-be1ea8363d06", - "metadata": {}, - "source": [ - "When indexing a `DataFrame`, pandas will not assume as readily the intention of your code. In this case, using a row label by itself will not work; **with `DataFrames`, labels are used for identifying columns**." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b504ed93-d310-4384-b99b-08d3ddc96bb0", - "metadata": {}, - "outputs": [], - "source": [ - "df[\"Nino34\"]" - ] - }, - { - "cell_type": "markdown", - "id": "34196c97-5117-402c-a1d6-c05298ed8500", - "metadata": {}, - "source": [ - "As shown below, you also cannot subset columns in a `DataFrame` using integer indices:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c393a116-da08-4b99-b87d-de76e2614f00", - "metadata": { - "tags": [ - "raises-exception" - ] - }, - "outputs": [], - "source": [ - "df[0]" - ] - }, - { - "cell_type": "markdown", - "id": "d4e6213d", - "metadata": {}, - "source": [ - "From earlier examples, we know that we can use an index or label with a `DataFrame` to pull out a column as a `Series`, and we know that we can use an index or label with a `Series` to pull out a single value. Therefore, by chaining brackets, we can pull any individual data value out of the `DataFrame`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c61fa6d4", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "df[\"Nino34\"][\"1982-04-01\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3bd7cf95", - "metadata": {}, - "outputs": [], - "source": [ - "df[\"Nino34\"][3]" - ] - }, - { - "cell_type": "markdown", - "id": "afb0d6ef", - "metadata": {}, - "source": [ - "However, subsetting data using this chained-bracket technique is not preferred by Pandas. As described above, Pandas prefers us to use the `.loc` and `.iloc` methods for subsetting. In addition, these methods provide a clearer, more efficient way to extract specific data from a `DataFrame`, as illustrated below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9fb5df7b", - "metadata": {}, - "outputs": [], - "source": [ - "df.loc[\"1982-04-01\", \"Nino34\"]" - ] - }, - { - "cell_type": "markdown", - "id": "98d6e445-51ed-4128-bfeb-fb82abbe9cb8", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " When using this syntax to pull individual data values from a DataFrame, make sure to list the row first, and then the column.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "e710252f", - "metadata": {}, - "source": [ - "The `.loc` and `.iloc` methods also allow us to pull entire rows out of a `DataFrame`, as shown in these examples:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aad4fde6", - "metadata": {}, - "outputs": [], - "source": [ - "df.loc[\"1982-04-01\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f93737ba", - "metadata": {}, - "outputs": [], - "source": [ - "df.loc[\"1982-01-01\":\"1982-12-01\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6c23cbca", - "metadata": {}, - "outputs": [], - "source": [ - "df.iloc[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22c07d7d", - "metadata": {}, - "outputs": [], - "source": [ - "df.iloc[0:12]" - ] - }, - { - "cell_type": "markdown", - "id": "4c2ed15e", - "metadata": {}, - "source": [ - "In the next example, we illustrate how you can use slices of rows and lists of columns to create a smaller `DataFrame` out of an existing `DataFrame`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8390a35b", - "metadata": {}, - "outputs": [], - "source": [ - "df.loc[\n", - " \"1982-01-01\":\"1982-12-01\", # slice of rows\n", - " [\"Nino12\", \"Nino3\", \"Nino4\", \"Nino34\"], # list of columns\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "c128cc18-e433-4060-870d-19835b5e556e", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " There are certain limitations to these subsetting techniques. For more information on these limitations, as well as a comparison of DataFrame and Series indexing methods, see the Pandas indexing documentation.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "2e2739dc", - "metadata": {}, - "source": [ - "## Exploratory Data Analysis\n", - "\n", - "### Get a Quick Look at the Beginning/End of your `DataFrame`\n", - "Pandas also gives you a few shortcuts to quickly investigate entire `DataFrames`. The `head` method shows the first five rows of a `DataFrame`, and the `tail` method shows the last five rows of a `DataFrame`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3c11b92a", - "metadata": {}, - "outputs": [], - "source": [ - "df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3bf87294", - "metadata": {}, - "outputs": [], - "source": [ - "df.tail()" - ] - }, - { - "cell_type": "markdown", - "id": "cba9f221", - "metadata": {}, - "source": [ - "### Quick Plots of Your Data\n", - "A good way to explore your data is by making a simple plot. Pandas contains its own `plot` method; this allows us to plot Pandas series without needing `matplotlib`. In this example, we plot the `Nino34` series of our `df` `DataFrame` in this way:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bf317171", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34.plot();" - ] - }, - { - "cell_type": "markdown", - "id": "99c2c7a3", - "metadata": {}, - "source": [ - "Before, we called `.plot()`, which generated a single line plot. Line plots can be helpful for understanding some types of data, but there are other types of data that can be better understood with different plot types. For example, if your data values form a distribution, you can better understand them using a histogram plot.\n", - "\n", - "The code for plotting histogram data differs in two ways from the code above for the line plot. First, two series are being used from the `DataFrame` instead of one. Second, after calling the `plot` method, we call an additional method called `hist`, which converts the plot into a histogram." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5f85e2dd", - "metadata": {}, - "outputs": [], - "source": [ - "df[['Nino12', 'Nino34']].plot.hist();" - ] - }, - { - "cell_type": "markdown", - "id": "a4e07618", - "metadata": {}, - "source": [ - "The histogram plot helped us better understand our data; there are clear differences in the distributions. To even better understand this type of data, it may also be helpful to create a box plot. This can be done using the same line of code, with one change: we call the `box` method instead of `hist`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6329d231", - "metadata": {}, - "outputs": [], - "source": [ - "df[['Nino12', 'Nino34']].plot.box();" - ] - }, - { - "cell_type": "markdown", - "id": "c338385b", - "metadata": {}, - "source": [ - "Just like the histogram plot, this box plot indicates a clear difference in the distributions. Using multiple types of plot in this way can be useful for verifying large datasets. The pandas plotting methods are capable of creating many different types of plots. To see how to use the plotting methods to generate each type of plot, please review the [pandas plot documentation](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html)." - ] - }, - { - "cell_type": "markdown", - "id": "69fc4078", - "metadata": {}, - "source": [ - "#### Customize your Plot\n", - "The pandas plotting methods are, in fact, wrappers for similar methods in matplotlib. This means that you can customize pandas plots by including keyword arguments to the plotting methods. These keyword arguments, for the most part, are equivalent to their matplotlib counterparts." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "da22f990", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34.plot(\n", - " color='black',\n", - " linewidth=2,\n", - " xlabel='Year',\n", - " ylabel='ENSO34 Index (degC)',\n", - " figsize=(8, 6),\n", - ");" - ] - }, - { - "cell_type": "markdown", - "id": "e7145ef6", - "metadata": {}, - "source": [ - "Although plotting data can provide a clear visual picture of data values, sometimes a more quantitative look at data is warranted. As elaborated on in the next section, this can be achieved using the `describe` method. The `describe` method is called on the entire `DataFrame`, and returns various summarized statistics for each column in the `DataFrame`.\n", - "### Basic Statistics\n", - "\n", - "We can garner statistics for a `DataFrame` by using the `describe` method. When this method is called on a `DataFrame`, a set of statistics is returned in tabular format. The columns match those of the `DataFrame`, and the rows indicate different statistics, such as minimum." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b5c27a8", - "metadata": {}, - "outputs": [], - "source": [ - "df.describe()" - ] - }, - { - "cell_type": "markdown", - "id": "a92bb8b3", - "metadata": {}, - "source": [ - "You can also view specific statistics using corresponding methods. In this example, we look at the mean values in the entire `DataFrame`, using the `mean` method. When such methods are called on the entire `DataFrame`, a `Series` is returned. The indices of this `Series` are the column names in the `DataFrame`, and the values are the calculated values (in this case, mean values) for the `DataFrame` columns." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db9e4a16", - "metadata": {}, - "outputs": [], - "source": [ - "df.mean()" - ] - }, - { - "cell_type": "markdown", - "id": "1ff5aec7", - "metadata": {}, - "source": [ - "If you want a specific statistic for only one column in the `DataFrame`, pull the column out of the `DataFrame` with dot notation, then call the statistic function (in this case, mean) on that column, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9aa38e59", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34.mean()" - ] - }, - { - "cell_type": "markdown", - "id": "7295e7b0", - "metadata": {}, - "source": [ - "### Subsetting Using the Datetime Column\n", - "\n", - "Slicing is a useful technique for subsetting a `DataFrame`, but there are also other options that can be equally useful. In this section, some of these additional techniques are covered.\n", - "\n", - "If your `DataFrame` uses datetime values for indices, you can select data from only one month using `df.index.month`. In this example, we specify the number 1, which only selects data from January." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a506724a", - "metadata": {}, - "outputs": [], - "source": [ - "# Uses the datetime column\n", - "df[df.index.month == 1]" - ] - }, - { - "cell_type": "markdown", - "id": "16d4e0e7", - "metadata": {}, - "source": [ - "This example shows how to create a new column containing the month portion of the datetime index for each data row. The value returned by `df.index.month` is used to obtain the data for this new column:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fe5d5ee4", - "metadata": {}, - "outputs": [], - "source": [ - "df['month'] = df.index.month" - ] - }, - { - "cell_type": "markdown", - "id": "8750443f", - "metadata": {}, - "source": [ - "This next example illustrates how to use the new month column to calculate average monthly values over the other data columns. First, we use the `groupby` method to group the other columns by the month. Second, we take the average (mean) to obtain the monthly averages. Finally, we plot the resulting data as a line plot by simply calling `plot()`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f94c91f9", - "metadata": {}, - "outputs": [], - "source": [ - "df.groupby('month').mean().plot();" - ] - }, - { - "cell_type": "markdown", - "id": "a0c9481b", - "metadata": {}, - "source": [ - "### Investigating Extreme Values" - ] - }, - { - "cell_type": "markdown", - "id": "fec15b77", - "metadata": {}, - "source": [ - "If you need to search for rows that meet a specific criterion, you can use **conditional indexing**. In this example, we search for rows where the Nino34 anomaly value (`Nino34anom`) is greater than 2:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "098fc88d", - "metadata": {}, - "outputs": [], - "source": [ - "df[df.Nino34anom > 2]" - ] - }, - { - "cell_type": "markdown", - "id": "f26bc439", - "metadata": {}, - "source": [ - "This example shows how to use the `sort_values` method on a `DataFrame`. This method sorts values in a `DataFrame` by the column specified as an argument." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8051c4f6", - "metadata": {}, - "outputs": [], - "source": [ - "df.sort_values('Nino34anom')" - ] - }, - { - "cell_type": "markdown", - "id": "a293de79", - "metadata": {}, - "source": [ - "You can also reverse the ordering of the sort by specifying the `ascending` keyword argument as `False`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "be7ff8ce", - "metadata": {}, - "outputs": [], - "source": [ - "df.sort_values('Nino34anom', ascending=False)" - ] - }, - { - "cell_type": "markdown", - "id": "5504a0da", - "metadata": {}, - "source": [ - "### Resampling\n", - "In these examples, we illustrate a process known as resampling. Using resampling, you can change the frequency of index data values, reducing so-called 'noise' in a data plot. This is especially useful when working with timeseries data; plots can be equally effective with resampled data in these cases. The resampling performed in these examples converts monthly values to yearly averages. This is performed by passing the value '1Y' to the `resample` method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "597cfeac", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34.plot();" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e3ee506", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34.resample('1Y').mean().plot();" - ] - }, - { - "cell_type": "markdown", - "id": "16c80788", - "metadata": {}, - "source": [ - "### Applying operations to a DataFrame\n", - "\n", - "One of the most commonly used features in Pandas is the performing of calculations to multiple data values in a `DataFrame` simultaneously. Let's first look at a familiar concept: a function that converts single values. The following example uses such a function to convert temperature values from degrees Celsius to Kelvin." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8afa857", - "metadata": {}, - "outputs": [], - "source": [ - "def convert_degc_to_kelvin(temperature_degc):\n", - " \"\"\"\n", - " Converts from degrees celsius to Kelvin\n", - " \"\"\"\n", - "\n", - " return temperature_degc + 273.15" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34892381", - "metadata": {}, - "outputs": [], - "source": [ - "# Convert a single value\n", - "convert_degc_to_kelvin(0)" - ] - }, - { - "cell_type": "markdown", - "id": "384a0bdb", - "metadata": {}, - "source": [ - "The following examples instead illustrate a new concept: using such functions with `DataFrames` and `Series`. For the first example, we start by creating a `Series`; in order to do so, we subset the `DataFrame` by the `Nino34` column. This has already been done earlier in this page; we do not need to create this `Series` again. We are using this particular `Series` for a reason: the data values are in degrees Celsius." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f09ee7c6", - "metadata": {}, - "outputs": [], - "source": [ - "nino34_series" - ] - }, - { - "cell_type": "markdown", - "id": "28ac04e8", - "metadata": {}, - "source": [ - "Here, we look at a portion of an existing `DataFrame` column. Notice that this column portion is a Pandas `Series`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8718a43f", - "metadata": {}, - "outputs": [], - "source": [ - "type(df.Nino12[0:10])" - ] - }, - { - "cell_type": "markdown", - "id": "ff1f569f", - "metadata": {}, - "source": [ - "As shown in the following example, each Pandas `Series` contains a representation of its data in numpy format. Therefore, it is possible to convert a Pandas `Series` into a numpy array; this is done using the `.values` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "61a8255f", - "metadata": {}, - "outputs": [], - "source": [ - "type(df.Nino12.values[0:10])" - ] - }, - { - "cell_type": "markdown", - "id": "2a5693fe", - "metadata": {}, - "source": [ - "This example illustrates how to use the temperature-conversion function defined above on a `Series` object. Just as calling the function with a single value returns a single value, calling the function on a `Series` object returns another `Series` object. The function performs the temperature conversion on each data value in the `Series`, and returns a `Series` with all values converted." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ae197a92", - "metadata": {}, - "outputs": [], - "source": [ - "convert_degc_to_kelvin(nino34_series)" - ] - }, - { - "cell_type": "markdown", - "id": "87871b82", - "metadata": {}, - "source": [ - "If we call the `.values` method on the `Series` passed to the function, the `Series` is converted to a numpy array, as described above. The function then converts each value in the numpy array, and returns a new numpy array with all values sorted." - ] - }, - { - "cell_type": "markdown", - "id": "84ec100b-60bd-4cb9-b596-40af2a04b95d", - "metadata": {}, - "source": [ - "
\n", - "

Warning

\n", - " It is recommended to only convert Series to NumPy arrays when necessary; doing so removes the label information that enables much of the Pandas core functionality.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "52ae68ee", - "metadata": {}, - "outputs": [], - "source": [ - "convert_degc_to_kelvin(nino34_series.values)" - ] - }, - { - "cell_type": "markdown", - "id": "65b3cd56", - "metadata": {}, - "source": [ - "As described above, when our temperature-conversion function accepts a `Series` as an argument, it returns a `Series`. We can directly assign this returned `Series` to a new column in our `DataFrame`, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2d84dfe1", - "metadata": {}, - "outputs": [], - "source": [ - "df['Nino34_degK'] = convert_degc_to_kelvin(nino34_series)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dd9a0811", - "metadata": {}, - "outputs": [], - "source": [ - "df.Nino34_degK" - ] - }, - { - "cell_type": "markdown", - "id": "a8c6dba3", - "metadata": {}, - "source": [ - "In this final example, we demonstrate the use of the `to_csv` method to save a `DataFrame` as a `.csv` file. This example also demonstrates the `read_csv` method, which reads `.csv` files into Pandas `DataFrames`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "054428db", - "metadata": {}, - "outputs": [], - "source": [ - "df.to_csv('nino_analyzed_output.csv')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3f7d378f", - "metadata": {}, - "outputs": [], - "source": [ - "pd.read_csv('nino_analyzed_output.csv', index_col=0, parse_dates=True)" - ] - }, - { - "cell_type": "markdown", - "id": "9327e958", - "metadata": {}, - "source": [ - "---\n", - "## Summary\n", - "* Pandas is a very powerful tool for working with tabular (i.e., spreadsheet-style) data\n", - "* There are multiple ways of subsetting your pandas dataframe or series\n", - "* Pandas allows you to refer to subsets of data by label, which generally makes code more readable and more robust\n", - "* Pandas can be helpful for exploratory data analysis, including plotting and basic statistics\n", - "* One can apply calculations to pandas dataframes and save the output via `csv` files\n", - "\n", - "### What's Next?\n", - "In the next notebook, we will look more into using pandas for more in-depth data analysis.\n", - "\n", - "## Resources and References\n", - "1. [NOAA NCDC ENSO Dataset Used in this Example](https://www.ncdc.noaa.gov/teleconnections/enso/indicators/sst/)\n", - "1. [Getting Started with Pandas](https://pandas.pydata.org/docs/getting_started/index.html#getting-started)\n", - "1. [Pandas User Guide](https://pandas.pydata.org/docs/user_guide/index.html#user-guide)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_preview/419/_sources/core/xarray.md b/_preview/419/_sources/core/xarray.md deleted file mode 100644 index 7e582ae65..000000000 --- a/_preview/419/_sources/core/xarray.md +++ /dev/null @@ -1,19 +0,0 @@ -![xarray Logo](http://xarray.pydata.org/en/stable/_static/dataset-diagram-logo.png 'xarray Logo') - -# Xarray - -This section contains tutorials on using [Xarray][xarray home]. Xarray is used widely in the geosciences and beyond for analysis of gridded N-dimensional datasets. - ---- - -From the [Xarray website][xarray home]: - -> Xarray (formerly Xray) is an open source project and Python package that makes working with labelled multi-dimensional arrays simple, efficient, and fun! -> -> Xarray introduces labels in the form of dimensions, coordinates and attributes on top of raw NumPy-like arrays, which allows for a more intuitive, more concise, and less error-prone developer experience. The package includes a large and growing library of domain-agnostic functions for advanced analytics and visualization with these data structures. -> -> Xarray is inspired by and borrows heavily from pandas, the popular data analysis package focused on labelled tabular data. It is particularly tailored to working with netCDF files, which were the source of xarray’s data model, and integrates tightly with dask for parallel computing. - -You should have a basic familiarity with [Numpy arrays](numpy) prior to working through the Xarray notebooks presented here. - -[xarray home]: http://xarray.pydata.org/en/stable/ diff --git a/_preview/419/_sources/core/xarray/computation-masking.ipynb b/_preview/419/_sources/core/xarray/computation-masking.ipynb deleted file mode 100644 index 5c7467dfd..000000000 --- a/_preview/419/_sources/core/xarray/computation-masking.ipynb +++ /dev/null @@ -1,907 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7b494629-f859-4586-b235-e61fed184b9a", - "metadata": { - "tags": [] - }, - "source": [ - "# Computations and Masks with Xarray" - ] - }, - { - "cell_type": "markdown", - "id": "b1fb677c-11f3-4901-a7df-3cd3f9e45b6e", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "79f94c0a-b585-4510-97da-379daea2b873", - "metadata": { - "tags": [] - }, - "source": [ - "## Overview\n", - "\n", - "In this tutorial, we will cover the following topics:\n", - "\n", - "1. Performing basic arithmetic on `DataArrays` and `Datasets`\n", - "2. Performing aggregation (i.e., reduction) along single or multiple dimensions of a `DataArray` or `Dataset`\n", - "3. Computing climatologies and anomalies of data using Xarray's \"split-apply-combine\" approach, via the `.groupby()` method\n", - "4. Performing weighted-reduction operations along single or multiple dimensions of a `DataArray` or `Dataset`\n", - "5. Providing a broad overview of Xarray's data-masking capability\n", - "6. Using the `.where()` method to mask Xarray data" - ] - }, - { - "cell_type": "markdown", - "id": "921b094b-d556-4a9e-a1bd-7a8560d8b335", - "metadata": { - "tags": [] - }, - "source": [ - "## Prerequisites\n", - "\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Introduction to Xarray](xarray-intro) | Necessary | |\n", - "\n", - "\n", - "- **Time to learn**: 60 minutes" - ] - }, - { - "cell_type": "markdown", - "id": "f6e62752-c323-4e24-8767-0754d1816556", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "0af7bee1-3de3-453a-8ae8-bcd7910b4266", - "metadata": { - "tags": [] - }, - "source": [ - "## Imports\n", - "\n", - "In order to work with data and plotting, we must import NumPy, Matplotlib, and Xarray. These packages are covered in greater detail in earlier tutorials. We also import a package that allows quick download of Pythia example datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06073287-7bdb-45b5-9cec-8cdf123adb49", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import xarray as xr\n", - "from pythia_datasets import DATASETS" - ] - }, - { - "cell_type": "markdown", - "id": "9719db5b-e645-4815-b8df-d454fa7703e7", - "metadata": {}, - "source": [ - "## Data Setup\n", - "\n", - "The bulk of the examples in this tutorial make use of a single dataset. This dataset contains monthly sea surface temperature (SST, call 'tos' here) data, and is obtained from the Community Earth System Model v2 (CESM2). (For this tutorial, however, the dataset will be retrieved from the Pythia example data repository.) The following example illustrates the process of retrieving this Global Climate Model dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7837f8bd-da89-4718-ab02-d5107576d2d6", - "metadata": {}, - "outputs": [], - "source": [ - "filepath = DATASETS.fetch('CESM2_sst_data.nc')\n", - "ds = xr.open_dataset(filepath)\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "id": "b3f4e108-f55e-4c25-a00d-99dc00ba849a", - "metadata": { - "tags": [] - }, - "source": [ - "## Arithmetic Operations\n", - "\n", - "In a similar fashion to NumPy arrays, performing an arithmetic operation on a `DataArray` will automatically perform the operation on all array values; this is known as vectorization. To illustrate the process of vectorization, the following example converts the air temperature data from units of degrees Celsius to units of Kelvin:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "09542eab-998d-4b2d-807c-dccd5bd4329e", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos + 273.15" - ] - }, - { - "cell_type": "markdown", - "id": "6f35c8d6-b0e6-4371-ad80-e182ffcec51b", - "metadata": {}, - "source": [ - "In addition, there are many other arithmetic operations that can be performed on `DataArrays`. In this example, we demonstrate squaring the original Celsius values of our air temperature data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "78c1ffc2-45cb-40cc-962e-c76021d9ab1c", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos**2" - ] - }, - { - "cell_type": "markdown", - "id": "0bebb17b-6906-4ba7-a4ff-c07a9206e790", - "metadata": { - "tags": [] - }, - "source": [ - "## Aggregation Methods \n", - "\n", - "A common practice in the field of data analysis is aggregation. Aggregation is the process of reducing data through methods such as `sum()`, `mean()`, `median()`, `min()`, and `max()`, in order to gain greater insight into the nature of large datasets. In this set of examples, we demonstrate correct usage of a select group of aggregation methods:" - ] - }, - { - "cell_type": "markdown", - "id": "a4d79093-f013-4821-84f8-3c223141046e", - "metadata": {}, - "source": [ - "Compute the mean:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "59b84034-7d42-4080-932f-0eefd165953d", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.mean()" - ] - }, - { - "cell_type": "markdown", - "id": "a75e0064-4363-4328-9a79-d87475ed1c81", - "metadata": {}, - "source": [ - "Notice that we did not specify the `dim` keyword argument; this means that the function was applied over all of the dataset's dimensions. In other words, the aggregation method computed the mean of every element of the temperature dataset across every temporal and spatial data point. However, if a dimension name is used with the `dim` keyword argument, the aggregation method computes an aggregation along the given dimension. In this next example, we use aggregation to calculate the temporal mean across all spatial data; this is performed by providing the dimension name `'time'` to the `dim` keyword argument:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a49b957e-ea24-414e-a422-c40a3723fbae", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.mean(dim='time').plot(size=7);" - ] - }, - { - "cell_type": "markdown", - "id": "109f77cf-54bb-4cac-a667-2afeb2cfef9d", - "metadata": {}, - "source": [ - "There are many other combinations of aggregation methods and dimensions on which to perform these methods. In this example, we compute the temporal minimum:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ceddf519-7459-4eaf-ae0d-69b2ba135317", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.min(dim=['time'])" - ] - }, - { - "cell_type": "markdown", - "id": "cb5f55c5-95bc-4fe3-a4fc-49958b6cf64c", - "metadata": {}, - "source": [ - "This example computes the spatial sum. Note that this dataset contains no altitude data; as such, the required spatial dimensions passed to the method consist only of latitude and longitude." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "487cafa8-c1cc-451a-96da-900c6ab961d5", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.sum(dim=['lat', 'lon'])" - ] - }, - { - "cell_type": "markdown", - "id": "b613fc00-5c75-4df1-b4e4-d391de84aab2", - "metadata": {}, - "source": [ - "For the last example in this set of aggregation examples, we compute the temporal median:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0873a32-de0e-456e-8948-e6516ddb7fd1", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.median(dim='time')" - ] - }, - { - "cell_type": "markdown", - "id": "b9790835-51ae-460b-87a4-618e7760d7a7", - "metadata": {}, - "source": [ - "In addition, there are many other commonly used aggregation methods in Xarray. Some of the more popular aggregation methods are summarized in the following table:\n", - "\n", - "| Aggregation | Description |\n", - "|--------------------------|---------------------------------|\n", - "| ``count()`` | Total number of items |\n", - "| ``mean()``, ``median()`` | Mean and median |\n", - "| ``min()``, ``max()`` | Minimum and maximum |\n", - "| ``std()``, ``var()`` | Standard deviation and variance |\n", - "| ``prod()`` | Compute product of elements |\n", - "| ``sum()`` | Compute sum of elements |\n", - "| ``argmin()``, ``argmax()``| Find index of minimum and maximum value |" - ] - }, - { - "cell_type": "markdown", - "id": "8704803f-300d-4631-a2fa-f62d18726d1c", - "metadata": { - "tags": [] - }, - "source": [ - "## GroupBy: Split, Apply, Combine\n", - "\n", - "While we can obtain useful summaries of datasets using simple aggregation methods, it is more often the case that aggregation must be performed over coordinate labels or groups. In order to perform this type of aggregation, it is helpful to use the **split-apply-combine** workflow. Fortunately, Xarray provides this functionality for `DataArrays` and `Datasets` by means of the `groupby` operation. The following figure illustrates the split-apply-combine workflow in detail:\n", - "\n", - "\n", - "\n", - "Based on the above figure, you can understand the split-apply-combine process performed by `groupby`. In detail, the steps of this process are:\n", - "\n", - "- The split step involves breaking up and grouping an xarray `Dataset` or `DataArray` depending on the value of the specified group key.\n", - "- The apply step involves computing some function, usually an aggregate, transformation, or filtering, within the individual groups.\n", - "- The combine step merges the results of these operations into an output xarray `Dataset` or `DataArray`.\n", - "\n", - "In this set of examples, we will remove the seasonal cycle (also known as a climatology) from our dataset using `groupby`. There are many types of input that can be provided to `groupby`; a full list can be found in [Xarray's `groupby` user guide](https://xarray.pydata.org/en/stable/user-guide/groupby.html)." - ] - }, - { - "cell_type": "markdown", - "id": "713cc8d8-7374-4c5b-be61-aec4b5b0ffe6", - "metadata": {}, - "source": [ - "In this first example, we plot data to illustrate the annual cycle described above. We first select the grid point closest to a specific latitude-longitude point. Once we have this grid point, we can plot a temporal series of sea-surface temperature (SST) data at that location. Reviewing the generated plot, the annual cycle of the data becomes clear." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0348ee8-6e9b-4f50-a844-375ae00d2771", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.sel(lon=310, lat=50, method='nearest').plot();" - ] - }, - { - "cell_type": "markdown", - "id": "d1505625-cbcd-495b-a15f-8824e455415b", - "metadata": {}, - "source": [ - "### Split\n", - "\n", - "The first step of the split-apply-combine process is splitting. As described above, this step involves splitting a dataset into groups, with each group matching a group key. In this example, we split the SST data using months as a group key. Therefore, there is one resulting group for January data, one for February data, etc. This code illustrates how to perform such a split:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6e4fb25e-165f-4350-a93d-46a344f2d175", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.groupby(ds.time.dt.month)" - ] - }, - { - "cell_type": "markdown", - "id": "5d176ad8-15f1-4ecc-ab3e-898cef3b4e18", - "metadata": {}, - "source": [ - "
\n", - "\n", - "In the above code example, we are extracting components of date/time data by way of the time coordinate's `.dt` attribute. This attribute is a `DatetimeAccessor` object that contains additional attributes for units of time, such as hour, day, and year. Since we are splitting the data into monthly data, we use the `month` attribute of .dt in this example. (In addition, there exists similar functionality in Pandas; see the [official documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.html) for details.)\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "ad273652-178c-4eda-80b6-6d39a11d6f1e", - "metadata": {}, - "source": [ - "In addition, there is a more concise syntax that can be used in specific instances. This syntax can be used if the variable on which the grouping is performed is already present in the dataset. The following example illustrates this syntax; it is functionally equivalent to the syntax used in the above example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c6990393-fb5f-4a10-b8e2-fd9c6917d9d2", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.groupby('time.month')" - ] - }, - { - "cell_type": "markdown", - "id": "6b85dbf7-daf1-4889-8b3b-6991d290969f", - "metadata": { - "tags": [] - }, - "source": [ - "### Apply & Combine \n", - "\n", - "Now that we have split our data into groups, the next step is to apply a calculation to the groups. There are two types of calculation that can be applied:\n", - "\n", - "- aggregation: reduces the size of the group\n", - "- transformation: preserves the group's full size\n", - "\n", - "After a calculation is applied to the groups, Xarray will automatically combine the groups back into a single object, completing the split-apply-combine workflow.\n", - "\n", - "\n", - "\n", - "#### Compute climatology \n", - "\n", - "\n", - "In this example, we use the split-apply-combine workflow to calculate the monthly climatology at every point in the dataset. Notice that we are using the `month` `DatetimeAccessor`, as described above, as well as the `.mean()` aggregation function:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e568c2f-7143-4346-85ce-a430db03316e", - "metadata": {}, - "outputs": [], - "source": [ - "tos_clim = ds.tos.groupby('time.month').mean()\n", - "tos_clim" - ] - }, - { - "cell_type": "markdown", - "id": "2ef90862-aeb4-45b3-87fb-e9df8f197c81", - "metadata": {}, - "source": [ - "Now that we have a `DataArray` containing the climatology data, we can plot the data in different ways. In this example, we plot the climatology at a specific latitude-longitude point:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f908c377-67fa-449c-b8d1-82ba6a14baff", - "metadata": {}, - "outputs": [], - "source": [ - "tos_clim.sel(lon=310, lat=50, method='nearest').plot();" - ] - }, - { - "cell_type": "markdown", - "id": "0e5dc34b-99bc-494b-9c04-ed8388ab2e6c", - "metadata": {}, - "source": [ - "In this example, we plot the zonal mean climatology:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22c61c11-2a48-4c6c-8009-6f20e0101237", - "metadata": {}, - "outputs": [], - "source": [ - "tos_clim.mean(dim='lon').transpose().plot.contourf(levels=12, cmap='turbo');" - ] - }, - { - "cell_type": "markdown", - "id": "3411ebb7-9831-4e52-ab2e-7e4e7a1356ee", - "metadata": {}, - "source": [ - "Finally, this example calculates and plots the difference between the climatology for January and the climatology for December:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "19a2808b-81f9-40e5-ab31-d63bfce85eae", - "metadata": {}, - "outputs": [], - "source": [ - "(tos_clim.sel(month=1) - tos_clim.sel(month=12)).plot(size=6, robust=True);" - ] - }, - { - "cell_type": "markdown", - "id": "266b8130-ca7d-4aec-a9a2-d7281ad64425", - "metadata": {}, - "source": [ - "#### Compute anomaly\n", - "\n", - "In this example, we compute the anomaly of the original data by removing the climatology from the data values. As shown in previous examples, the climatology is first calculated. The calculated climatology is then removed from the data using arithmetic and Xarray's `groupby` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c9940df-5174-49bf-9117-eef1e14abec0", - "metadata": {}, - "outputs": [], - "source": [ - "gb = ds.tos.groupby('time.month')\n", - "tos_anom = gb - gb.mean(dim='time')\n", - "tos_anom" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35fc2054-df48-4ea2-8433-632ba8755c61", - "metadata": {}, - "outputs": [], - "source": [ - "tos_anom.sel(lon=310, lat=50, method='nearest').plot();" - ] - }, - { - "cell_type": "markdown", - "id": "c3c087dc-966d-48a0-bb99-ca63cf20ff05", - "metadata": {}, - "source": [ - "In this example, we compute and plot our dataset's mean global anomaly over time. In order to specify global data, we must provide both `lat` and `lon` to the `mean()` method's `dim` keyword argument:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5d3abf06-a341-45ac-a3f2-76131016c0b3", - "metadata": {}, - "outputs": [], - "source": [ - "unweighted_mean_global_anom = tos_anom.mean(dim=['lat', 'lon'])\n", - "unweighted_mean_global_anom.plot();" - ] - }, - { - "cell_type": "markdown", - "id": "d9f768be-a960-4417-bb1e-9785ca9ca4ea", - "metadata": {}, - "source": [ - "
\n", - " \n", - "\n", - "Many geoscientific algorithms perform operations over data contained in many different grid cells. However, if the grid cells are not equivalent in size, the operation is not scientifically valid by default. Fortunately, this can be fixed by weighting the data in each grid cell by the size of the cell. Weighting data in Xarray is simple, as Xarray has a built-in weighting method, known as [`.weighted()`](https://xarray.pydata.org/en/stable/user-guide/computation.html#weighted-array-reductions).\n", - "\n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "id": "908bcc38-bf93-478c-99e4-8bbafeec1f21", - "metadata": {}, - "source": [ - "In this example, we again make use of the Pythia example data library to load a new CESM2 dataset. Contained in this dataset are weights corresponding to the grid cells in our anomaly data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a5878de6-f3ab-43e0-8f0d-12ab51631450", - "metadata": {}, - "outputs": [], - "source": [ - "filepath2 = DATASETS.fetch('CESM2_grid_variables.nc')\n", - "areacello = xr.open_dataset(filepath2).areacello\n", - "areacello" - ] - }, - { - "cell_type": "markdown", - "id": "8a73a748-46b4-4350-b167-32725eebaec8", - "metadata": {}, - "source": [ - "In a similar fashion to a previous example, this example calculates mean global anomaly. However, this example makes use of the `.weighted()` method and the newly loaded CESM2 dataset to weight the grid cell data as described above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8f7e3a5-0748-4395-95b0-0e31d0a5d4d1", - "metadata": {}, - "outputs": [], - "source": [ - "weighted_mean_global_anom = tos_anom.weighted(areacello).mean(dim=['lat', 'lon'])" - ] - }, - { - "cell_type": "markdown", - "id": "17da2e3a-3ca6-41f4-892e-b26021c492e6", - "metadata": {}, - "source": [ - "This example plots both unweighted and weighted mean data, which illustrates the degree of scientific error with unweighted data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "802c5e99-7223-49b5-a867-91d943075d52", - "metadata": {}, - "outputs": [], - "source": [ - "unweighted_mean_global_anom.plot(size=7)\n", - "weighted_mean_global_anom.plot()\n", - "plt.legend(['unweighted', 'weighted']);" - ] - }, - { - "cell_type": "markdown", - "id": "3045c67e-21cd-4ef9-a49f-e12ae7db23cf", - "metadata": { - "tags": [] - }, - "source": [ - "## Other high level computation functionality\n", - "\n", - "- `resample`: [This method behaves similarly to groupby, but is specialized for time dimensions, and can perform temporal upsampling and downsampling.](https://xarray.pydata.org/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", - "- `rolling`: [This method is used to compute aggregation functions, such as `mean`, on moving windows of data in a dataset.](https://xarray.pydata.org/en/stable/user-guide/computation.html#rolling-window-operations)\n", - "- `coarsen`: [This method provides generic functionality for performing downsampling operations on various types of data.](https://xarray.pydata.org/en/stable/user-guide/computation.html#coarsen-large-arrays)" - ] - }, - { - "cell_type": "markdown", - "id": "eaf4dc7d-dfac-419e-a875-fc0c70fcd08c", - "metadata": {}, - "source": [ - "This example illustrates the resampling of a dataset's time dimension to annual frequency:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a9cfb76b-c4ab-441e-a474-c66b7af944ad", - "metadata": {}, - "outputs": [], - "source": [ - "r = ds.tos.resample(time='AS')\n", - "r" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6927f5be-d313-4d03-bab8-d22b3cb13899", - "metadata": {}, - "outputs": [], - "source": [ - "r.mean()" - ] - }, - { - "cell_type": "markdown", - "id": "32370e4f-a1c4-4163-a926-b9e3a8d6d5c2", - "metadata": {}, - "source": [ - "This example illustrates using the `rolling` method to compute averages in a moving window of 5 months of data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "342acbf1-4eee-4d0d-bb52-b394ffcd556d", - "metadata": {}, - "outputs": [], - "source": [ - "m_avg = ds.tos.rolling(time=5, center=True).mean()\n", - "m_avg" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0eb0cc4e-661a-4ab1-96ad-e096917ef104", - "metadata": {}, - "outputs": [], - "source": [ - "lat = 50\n", - "lon = 310\n", - "\n", - "m_avg.isel(lat=lat, lon=lon).plot(size=6)\n", - "ds.tos.isel(lat=lat, lon=lon).plot()\n", - "plt.legend(['5-month moving average', 'monthly data']);" - ] - }, - { - "cell_type": "markdown", - "id": "da76db37-e833-42c5-a740-5dcf0877b43c", - "metadata": { - "tags": [] - }, - "source": [ - "## Masking Data\n" - ] - }, - { - "cell_type": "markdown", - "id": "8a657ca8-fa2d-409c-9aaf-580828671018", - "metadata": { - "tags": [] - }, - "source": [ - "Masking of data can be performed in Xarray by providing single or multiple conditions to either Xarray's `.where()` method or a `Dataset` or `DataArray`'s `.where()` method. Data values matching the condition(s) are converted into a single example value, effectively masking them from the scientifically important data. In the following set of examples, we use the `.where()` method to mask various data values in the `tos` `DataArray`." - ] - }, - { - "cell_type": "markdown", - "id": "59a59bef-b08c-4e0f-a48a-894565a962e7", - "metadata": {}, - "source": [ - "For reference, we will first print our entire sea-surface temperature (SST) dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "afff083b-6c0b-4756-b1be-716a443d98a2", - "metadata": {}, - "outputs": [], - "source": [ - "ds" - ] - }, - { - "cell_type": "markdown", - "id": "386c7a1b-1a47-4f52-a42d-27fa997427d3", - "metadata": {}, - "source": [ - "### Using `where` with one condition" - ] - }, - { - "cell_type": "markdown", - "id": "a8d48b6b-a40e-469f-861f-83d943d70f03", - "metadata": {}, - "source": [ - "In this set of examples, we are trying to analyze data at the last temporal value in the dataset. This first example illustrates the use of `.isel()` to perform this analysis:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ac3e42eb-1852-4580-9c52-e7237135ed01", - "metadata": {}, - "outputs": [], - "source": [ - "sample = ds.tos.isel(time=-1)\n", - "sample" - ] - }, - { - "cell_type": "markdown", - "id": "ccdd1fa6-93fd-490d-8b05-c222ddcf953a", - "metadata": {}, - "source": [ - "As shown in the previous example, methods like `.isel()` and `.sel()` return data of a different shape than the original data provided to them. However, `.where()` preserves the shape of the original data by masking the values with a Boolean condition. Data values for which the condition is `True` are returned identical to the values passed in. On the other hand, data values for which the condition is `False` are returned as a preset example value. (This example value defaults to `nan`, but can be set to other values as well.)\n", - "\n", - "Before testing `.where()`, it is helpful to look at the [official documentation](http://xarray.pydata.org/en/stable/generated/xarray.DataArray.where.html). As stated above, the `.where()` method takes a Boolean condition. (Boolean conditions use operators such as less-than, greater-than, and equal-to, and return a value of `True` or `False`.) Most uses of `.where()` check whether or not specific data values are less than or greater than a constant value. As stated in the documentation, the data values specified in the Boolean condition of `.where()` can be any of the following:\n", - "\n", - "- a `DataArray`\n", - "- a `Dataset`\n", - "- a function\n", - "\n", - "In the following example, we make use of `.where()` to mask data with temperature values greater than `0`. Therefore, values greater than `0` are set to `nan`, as described above. (It is important to note that the Boolean condition matches values to keep, not values to mask out.)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "61abc5b3-aadf-4a96-98a2-c9c36094a863", - "metadata": {}, - "outputs": [], - "source": [ - "masked_sample = sample.where(sample < 0.0)\n", - "masked_sample" - ] - }, - { - "cell_type": "markdown", - "id": "09aeeee1-3924-4ccd-9b69-1be396c496b9", - "metadata": {}, - "source": [ - "In this example, we use Matplotlib to plot the original, unmasked data, as well as the masked data created in the previous example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "91457518-fc38-42e2-8b96-c5786e36f33f", - "metadata": {}, - "outputs": [], - "source": [ - "fig, axes = plt.subplots(ncols=2, figsize=(19, 6))\n", - "sample.plot(ax=axes[0])\n", - "masked_sample.plot(ax=axes[1]);" - ] - }, - { - "cell_type": "markdown", - "id": "4dd6b000-b079-461c-9a0e-8fd2bced814b", - "metadata": { - "tags": [] - }, - "source": [ - "### Using `where` with multiple conditions" - ] - }, - { - "cell_type": "markdown", - "id": "538bd497-3059-4f6e-9c48-5104958f8528", - "metadata": {}, - "source": [ - "Those familiar with Boolean conditions know that such conditions can be combined by using logical operators. In the case of `.where()`, the relevant logical operators are bitwise or exclusive `'and'` (represented by the `&` symbol) and bitwise or exclusive 'or' (represented by the `|` symbol). This allows multiple masking conditions to be specified in a single use of `.where()`; however, be aware that if multiple conditions are specified in this way, each simple Boolean condition must be enclosed in parentheses. (If you are not familiar with Boolean conditions, or this section is confusing in any way, please review a detailed Boolean expression guide before continuing with the tutorial.) In this example, we provide multiple conditions to `.where()` using a more complex Boolean condition. This allows us to mask locations with temperature values less than 25, as well as locations with temperature values greater than 30. (As stated above, the Boolean condition matches values to keep, and everything else is masked out. Because we are now using more complex Boolean conditions, understanding the following example may be difficult. Please review a Boolean condition guide if needed.)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c9bf1c46-e7ed-43c1-8a45-e03d22295da1", - "metadata": {}, - "outputs": [], - "source": [ - "sample.where((sample > 25) & (sample < 30)).plot(size=6);" - ] - }, - { - "cell_type": "markdown", - "id": "d1796fc8-039b-4c40-a6f4-b3a00c130770", - "metadata": {}, - "source": [ - "In addition to using `DataArrays` and `Datasets` in Boolean conditions provided to `.where()`, we can also use coordinate variables. In the following example, we make use of Boolean conditions containing `latitude` and `longitude` coordinates. This greatly simplifies the masking of regions outside of the [Niño 3.4 region](https://www.ncdc.noaa.gov/teleconnections/enso/indicators/sst/):\n", - "\n", - "![](https://www.ncdc.noaa.gov/monitoring-content/teleconnections/nino-regions.gif)\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "090f1997-8dea-4eed-aa55-ab3a180ecdd5", - "metadata": {}, - "outputs": [], - "source": [ - "sample.where(\n", - " (sample.lat < 5) & (sample.lat > -5) & (sample.lon > 190) & (sample.lon < 240)\n", - ").plot(size=6);" - ] - }, - { - "cell_type": "markdown", - "id": "47ebbbff-2409-43e1-90e9-2cd4c9777bdc", - "metadata": {}, - "source": [ - "### Using `where` with a custom fill value" - ] - }, - { - "cell_type": "markdown", - "id": "a1b76d95-f8b3-44ef-a7a5-c2028daaf500", - "metadata": {}, - "source": [ - "In the previous examples that make use of `.where()`, the masked data values are set to `nan`. However, this behavior can be modified by providing a second value, in numeric form, to `.where()`; if this numeric value is provided, it will be used instead of `nan` for masked data values. In this example, masked data values are set to `0` by providing a second value of `0` to the `.where()` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aae12476-4802-42a2-993b-29da6c383535", - "metadata": {}, - "outputs": [], - "source": [ - "sample.where((sample > 25) & (sample < 30), 0).plot(size=6);" - ] - }, - { - "cell_type": "markdown", - "id": "5fad83ca-faf6-44c5-8d05-173b425118e1", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "d2cb7356-714b-45cd-b109-d0b6965b6a0c", - "metadata": { - "tags": [] - }, - "source": [ - "## Summary \n", - "\n", - "- In a similar manner to NumPy arrays, performing arithmetic on a `DataArray` affects all values simultaneously.\n", - "- Xarray allows for simple data aggregation, over single or multiple dimensions, by way of built-in methods such as `sum()` and `mean()`.\n", - "- Xarray supports the useful split-apply-combine workflow through the `groupby` method.\n", - "- Xarray allows replacing (masking) of data matching specific Boolean conditions by means of the `.where()` method.\n", - "\n", - "### What's next?\n", - "\n", - "The next tutorial illustrates the use of previously covered Xarray concepts in a geoscientifically relevant example: plotting the [Niño 3.4 Index](https://climatedataguide.ucar.edu/climate-data/nino-sst-indices-nino-12-3-34-4-oni-and-tni)." - ] - }, - { - "cell_type": "markdown", - "id": "374de3a3-807a-47be-9014-e1af98909456", - "metadata": {}, - "source": [ - "## Resources and References\n", - "\n", - "- `groupby`: [Useful for binning/grouping data and applying reductions and/or transformations on those groups](https://xarray.pydata.org/en/stable/user-guide/groupby.html)\n", - "- `resample`: [Functionality similar to groupby, specialized for time dimensions. Can be used for temporal upsampling and downsampling](https://xarray.pydata.org/en/stable/user-guide/time-series.html#resampling-and-grouped-operations)\n", - "- `rolling`: [Useful for computing aggregations on moving windows of your dataset, e.g., computing moving averages](https://xarray.pydata.org/en/stable/user-guide/computation.html#rolling-window-operations)\n", - "- `coarsen`: [Generic functionality for downsampling data](https://xarray.pydata.org/en/stable/user-guide/computation.html#coarsen-large-arrays)\n", - "\n", - "- `weighted`: [Useful for weighting data before applying reductions](https://xarray.pydata.org/en/stable/user-guide/computation.html#weighted-array-reductions)\n", - "\n", - "- [More xarray tutorials and videos](https://xarray.pydata.org/en/stable/tutorials-and-videos.html)\n", - "- [Xarray Documentation - Masking with `where()`](https://xarray.pydata.org/en/stable/user-guide/indexing.html#masking-with-where)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_preview/419/_sources/core/xarray/dask-arrays-xarray.ipynb b/_preview/419/_sources/core/xarray/dask-arrays-xarray.ipynb deleted file mode 100644 index 1aa3b2ea3..000000000 --- a/_preview/419/_sources/core/xarray/dask-arrays-xarray.ipynb +++ /dev/null @@ -1,712 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "d59e6a58-b50e-4015-bbd8-b48608d44b26", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "013dde55-1cea-4fd8-b980-0fa06bdd5568", - "metadata": {}, - "source": [ - "# Dask Arrays with Xarray\n", - "\n", - "The scientific Python package known as Dask provides Dask Arrays: parallel, larger-than-memory, n-dimensional arrays that make use of blocked algorithms. They are analogous to Numpy arrays, but are distributed. These terms are defined below:\n", - "\n", - "* **Parallel** code uses many or all of the cores on the computer running the code.\n", - "* **Larger-than-memory** refers to algorithms that break up data arrays into small pieces, operate on these pieces in an optimized fashion, and stream data from a storage device. This allows a user or programmer to work with datasets of a size larger than the available memory.\n", - "* A **blocked algorithm** speeds up large computations by converting them into a series of smaller computations.\n", - "\n", - "In this tutorial, we cover the use of Xarray to wrap Dask arrays. By using Dask arrays instead of Numpy arrays in Xarray data objects, it becomes possible to execute analysis code in parallel with much less code and effort.\n", - "\n", - "\n", - "## Learning Objectives\n", - "\n", - "- Learn the distinction between *eager* and *lazy* execution, and performing both types of execution with Xarray\n", - "- Understand key features of Dask Arrays\n", - "- Learn to perform operations with Dask Arrays in similar ways to performing operations with NumPy arrays\n", - "- Understand the use of Xarray `DataArrays` and `Datasets` as \"Dask collections\", and the use of top-level Dask functions such as `dask.visualize()` on such collections\n", - "- Understand the ability to use Dask transparently in all built-in Xarray operations\n", - "\n", - "## Prerequisites\n", - "\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Introduction to NumPy](../numpy/numpy-basics) | Necessary | Familiarity with Data Arrays |\n", - "| [Introduction to Xarray](xarray-intro) | Necessary | Familiarity with Xarray Data Structures |\n", - "\n", - "\n", - "- **Time to learn**: *30-40 minutes*\n" - ] - }, - { - "cell_type": "markdown", - "id": "027ccc87-420b-45dd-830a-38766dd6248f", - "metadata": {}, - "source": [ - "## Imports\n", - "\n", - "For this tutorial, as we are working with Dask, there are a number of Dask packages that must be imported. Also, this is technically an Xarray tutorial, so Xarray and NumPy must also be imported. Finally, the Pythia datasets package is imported, allowing access to the Project Pythia example data library." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8aa666b9-4af2-41b5-8e77-e28f9cecddd1", - "metadata": {}, - "outputs": [], - "source": [ - "import dask\n", - "import dask.array as da\n", - "import numpy as np\n", - "import xarray as xr\n", - "from dask.diagnostics import ProgressBar\n", - "from dask.utils import format_bytes\n", - "from pythia_datasets import DATASETS" - ] - }, - { - "cell_type": "markdown", - "id": "259bdc60-6e96-4258-8d71-e733ce2d9aca", - "metadata": {}, - "source": [ - "## Blocked algorithms\n", - "\n", - "As described above, the definition of \"blocked algorithm\" is an algorithm that replaces a large operation with many small operations. In the case of datasets, this means that a blocked algorithm separates a dataset into chunks, and performs an operation on each.\n", - "\n", - "As an example of how blocked algorithms work, consider a dataset containing a billion numbers, and assume that the sum of the numbers is needed. Using a non-blocked algorithm, all of the numbers are added in one operation, which is extremely inefficient. However, by using a blocked algorithm, the dataset is broken into chunks. (For the purposes of this example, assume that 1,000 chunks are created, with 1,000,000 numbers each.) The sum of the numbers in each chunk is taken, most likely in parallel, and then each of those sums are summed to obtain the final result.\n", - "\n", - "By using blocked algorithms, we achieve the result, in this case one sum of one billion numbers, through the results of many smaller operations, in this case one thousand sums of one million numbers each. (Also note that each of the one thousand sums must then be summed, making the total number of sums 1,001.) This allows for a much greater degree of parallelism, potentially speeding up the code execution dramatically." - ] - }, - { - "cell_type": "markdown", - "id": "53b69958-355f-4121-a644-227adc1b14ef", - "metadata": {}, - "source": [ - "### `dask.array` contains these algorithms\n", - "\n", - "The main object type used in Dask is `dask.array`, which implements a subset of the `ndarray` (NumPy array) interface. However, unlike `ndarray`, `dask.array` uses blocked algorithms, which break up the array into smaller arrays, as described above. This allows for the execution of computations on arrays larger than memory, by using parallelism to divide the computation among multiple cores. Dask manages and coordinates blocked algorithms for any given computation by using Dask graphs, which lay out in detail the steps Dask takes to solve a problem. In addition, `dask.array` objects, known as Dask Arrays, are **lazy**; in other words, any computation performed on them is delayed until a specific method is called." - ] - }, - { - "cell_type": "markdown", - "id": "1aef9368-240b-423b-a3c7-0ab7baa8fa13", - "metadata": {}, - "source": [ - "### Create a `dask.array` object\n", - "\n", - "As stated earlier, Dask Arrays are loosely based on NumPy arrays. In the next set of examples, we illustrate the main differences between Dask Arrays and NumPy arrays. In order to illustrate the differences, we must have both a Dask Array object and a NumPy array object. Therefore, this first example creates a 3-D NumPy array of random data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "690bc749-976e-4b78-a801-8d01ee363ad7", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "shape = (600, 200, 200)\n", - "arr = np.random.random(shape)\n", - "arr" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c36032d1-0fb6-43d2-a188-87e8e00bd5a4", - "metadata": {}, - "outputs": [], - "source": [ - "format_bytes(arr.nbytes)" - ] - }, - { - "cell_type": "markdown", - "id": "8e7e54dc-342c-4d31-902a-ece54a813e7e", - "metadata": {}, - "source": [ - "As shown above, this NumPy array contains about 183 MB of data." - ] - }, - { - "cell_type": "markdown", - "id": "905dffb1-0879-4842-8b76-2538592f6156", - "metadata": {}, - "source": [ - "As stated above, we must also create a Dask Array. This next example creates a Dask Array with the same dimension sizes as the existing NumPy array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "be84728a-7953-4561-aa7d-326f4a45e3aa", - "metadata": {}, - "outputs": [], - "source": [ - "darr = da.random.random(shape, chunks=(300, 100, 200))" - ] - }, - { - "cell_type": "markdown", - "id": "632e9f08-3142-46d7-b457-e9bde0d1dce9", - "metadata": {}, - "source": [ - "By specifying values to the `chunks` keyword argument, we can specify the array pieces that Dask's blocked algorithms break the array into; in this case, we specify `(300, 100, 200)`." - ] - }, - { - "cell_type": "markdown", - "id": "ebbafe88-bb79-436c-aa3b-a9c5f31ff1ec", - "metadata": {}, - "source": [ - "
\n", - "

Specifying Chunks

\n", - " In this tutorial, we specify Dask Array chunks in a block shape. However, there are many additional ways to specify chunks; see this documentation for more details.\n", - "\n", - "
\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "ed467b75-9629-4fbc-a235-866459e4b881", - "metadata": {}, - "source": [ - "If you are viewing this page as a Jupyter Notebook, the next Jupyter cell will produce a rich information graphic giving in-depth details about the array and each individual chunk." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "42c13417-a5a2-4fd2-8610-a3cd70f4b93a", - "metadata": {}, - "outputs": [], - "source": [ - "darr" - ] - }, - { - "cell_type": "markdown", - "id": "75988622-8ec1-45b0-a069-29ca74f53836", - "metadata": {}, - "source": [ - "The above graphic contains a symbolic representation of the array, including `shape`, `dtype`, and `chunksize`. (Your view may be different, depending on how you are accessing this page.) Notice that there is no data shown for this array; this is because Dask Arrays are lazy, as described above. Before we call a compute method for this array, we first illustrate the structure of a Dask graph. In this example, we show the Dask graph by calling `.visualize()` on the array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "44a7a6e4-bcfc-40c1-a095-8fa315bfef4e", - "metadata": {}, - "outputs": [], - "source": [ - "darr.visualize()" - ] - }, - { - "cell_type": "markdown", - "id": "37f4038c-f2b2-40c3-91cd-af2713dd23df", - "metadata": {}, - "source": [ - "As shown in the above Dask graph, our array has four chunks, each one created by a call to NumPy's \"random\" method (`np.random.random`). These chunks are concatenated into a single array after the calculation is performed." - ] - }, - { - "cell_type": "markdown", - "id": "c5b29134-62af-4a0b-8e44-9f99b5072b45", - "metadata": {}, - "source": [ - "### Manipulate a `dask.array` object as you would a numpy array\n", - "\n", - "\n", - "We can perform computations on the Dask Array created above in a similar fashion to NumPy arrays. These computations include arithmetic, slicing, and reductions, among others.\n", - "\n", - "Although the code for performing these computations is similar between NumPy arrays and Dask Arrays, the process by which they are performed is quite different. For example, it is possible to call `sum()` on both a NumPy array and a Dask Array; however, these two `sum()` calls are definitely not the same, as shown below.\n", - "\n", - "#### What's the difference?\n", - "\n", - "When `sum()` is called on a Dask Array, the computation is not performed; instead, an expression of the computation is built. The `sum()` computation, as well as any other computation methods called on the same Dask Array, are not performed until a specific method (known as a compute method) is called on the array. (This is known as **lazy execution**.) On the other hand, calling `sum()` on a NumPy array performs the calculation immediately; this is known as **eager execution**.\n", - "\n", - "#### Why the difference?\n", - "\n", - "As described earlier, a Dask Array is divided into chunks. Any computations run on the Dask Array run on each chunk individually. If the result of the computation is obtained before the computation runs through all of the chunks, Dask can stop the computation to save CPU time and memory resources.\n", - "\n", - "This example illustrates calling `sum()` on a Dask Array; it also includes a demonstration of lazy execution, as well as another Dask graph display:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cf14f2f0-a66e-4578-8a8d-f0c7701beb9c", - "metadata": {}, - "outputs": [], - "source": [ - "total = darr.sum()\n", - "total" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36724be4-63ec-4b1c-9a9e-1a605a12a5a5", - "metadata": {}, - "outputs": [], - "source": [ - "total.visualize()" - ] - }, - { - "cell_type": "markdown", - "id": "09186a60-51fc-49c1-91ac-3614af0202cc", - "metadata": {}, - "source": [ - "#### Compute the result\n", - "\n", - "As described above, Dask Array objects make use of lazy execution. Therefore, operations performed on a Dask Array wait to execute until a compute method is called. As more operations are queued in this way, the Dask Array's Dask graph increases in complexity, reflecting the steps Dask will take to perform all of the queued operations. \n", - "\n", - "In this example, we call a compute method, simply called `.compute()`, to run on the Dask Array all of the stored computations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36aff31f-2f06-4703-a2a6-cf692ab5eed3", - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "total.compute()" - ] - }, - { - "cell_type": "markdown", - "id": "af09c610-0e66-4ce8-a53a-fdd50471271d", - "metadata": {}, - "source": [ - "### Exercise with `dask.arrays`\n", - "\n", - "In this section of the page, the examples are hands-on exercises pertaining to Dask Arrays. If these exercises are not interesting to you, this section can be used strictly as examples regardless of how the page is viewed. However, if you wish to participate in the exercises, make sure that you are viewing this page as a Jupyter Notebook.\n", - "\n", - "For the first exercise, modify the chunk size or shape of the Dask Array created earlier. Call `.sum()` on the modified Dask Array, and visualize the Dask graph to view the changes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11922e84-f13e-4db6-a8a0-cf75a5727cfb", - "metadata": {}, - "outputs": [], - "source": [ - "da.random.random(shape, chunks=(50, 200, 400)).sum().visualize()" - ] - }, - { - "cell_type": "markdown", - "id": "b275b5cc-51a6-48ff-a0a4-62bdc43e6530", - "metadata": {}, - "source": [ - "As is obvious from the above exercise, Dask quickly and easily determines a strategy for performing the operations, in this case a sum. This illustrates the appeal of Dask: automatic algorithm generation that scales from simple arithmetic problems to highly complex scientific equations with large datasets and multiple operations." - ] - }, - { - "cell_type": "markdown", - "id": "7a7dcaaa-6a6e-4f58-aa80-2890136158fd", - "metadata": {}, - "source": [ - "In this next set of examples, we demonstrate that increasing the complexity of the operations performed also increases the complexity of the Dask graph.\n", - "\n", - "In this example, we use randomly selected functions, arguments and Python slices to create a complex set of operations. We then visualize the Dask graph to illustrate the increased complexity:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e74a6817-8d06-4dd1-afec-98c53a8ae52a", - "metadata": {}, - "outputs": [], - "source": [ - "z = darr.dot(darr.T).mean(axis=0)[::2, :].std(axis=1)\n", - "z" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a4ccffad-5bda-4108-a6c8-6628510f8363", - "metadata": {}, - "outputs": [], - "source": [ - "z.visualize()" - ] - }, - { - "cell_type": "markdown", - "id": "35c59c2f-3e5c-443b-908f-4f14535d2802", - "metadata": {}, - "source": [ - "### Testing a bigger calculation\n", - "\n", - "While the earlier examples in this tutorial described well the basics of Dask, the size of the data in those examples, about 180 MB, is far too small for an actual use of Dask.\n", - "\n", - "In this example, we create a much larger array, more indicative of data actually used in Dask:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "482fb0fe-87d4-46fa-bb9d-ed38cc71d834", - "metadata": {}, - "outputs": [], - "source": [ - "darr = da.random.random((4000, 100, 4000), chunks=(1000, 100, 500)).astype('float32')\n", - "darr" - ] - }, - { - "cell_type": "markdown", - "id": "6c0f8b73-41c1-49fb-9fa4-97cb10ae6f4d", - "metadata": {}, - "source": [ - "The dataset created in the previous example is much larger, approximately 6 GB. Depending on how many programs are running on your computer, this may be greater than the amount of free RAM on your computer. However, as Dask is larger-than-memory, the amount of free RAM does not impede Dask's ability to work on this dataset.\n", - "\n", - "In this example, we again perform randomly selected operations, but this time on the much larger dataset. We also visualize the Dask graph, and then run the compute method. However, as computing complex functions on large datasets is inherently time-consuming, we show a progress bar to track the progress of the computation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "51e9addb-cc13-46f5-b542-827f8bdd94b5", - "metadata": {}, - "outputs": [], - "source": [ - "z = (darr + darr.T)[::2, :].mean(axis=2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c44ed57c-2a31-4df1-897b-02b614279755", - "metadata": {}, - "outputs": [], - "source": [ - "z.visualize()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f5bf25c1-7384-4953-bbb8-be0c3c4e02e9", - "metadata": {}, - "outputs": [], - "source": [ - "with ProgressBar():\n", - " computed_ds = z.compute()" - ] - }, - { - "cell_type": "markdown", - "id": "116996d8-cc8a-4201-94d9-8152f4b6aa42", - "metadata": {}, - "source": [ - "## Dask Arrays with Xarray\n", - "\n", - "While directly interacting with Dask Arrays can be useful on occasion, more often than not Dask Arrays are interacted with through [Xarray](http://xarray.pydata.org/en/stable/\n", - "). Since Xarray wraps NumPy arrays, and Dask Arrays contain most of the functionality of NumPy arrays, Xarray can also wrap Dask Arrays, allowing anyone with knowledge of Xarray to easily start using the Dask interface." - ] - }, - { - "cell_type": "markdown", - "id": "57d12474-e71a-400f-b103-824f0de7288b", - "metadata": {}, - "source": [ - "### Reading data with `Dask` and `Xarray`\n", - "\n", - "As demonstrated in previous examples, a Dask Array consists of many smaller arrays, called chunks:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d6e932e0-ff01-412b-9629-1fb5590ffb0a", - "metadata": {}, - "outputs": [], - "source": [ - "darr" - ] - }, - { - "cell_type": "markdown", - "id": "4d7e0c25-3baa-45aa-bb4d-940480b2fbe9", - "metadata": {}, - "source": [ - "As shown in the following example, to read data into Xarray as Dask Arrays, simply specify the `chunks` keyword argument when calling the `open_dataset()` function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3396e4e6-911b-4c40-a3f8-cdccf034a4ee", - "metadata": {}, - "outputs": [], - "source": [ - "ds = xr.open_dataset(DATASETS.fetch('CESM2_sst_data.nc'), chunks={})\n", - "ds.tos" - ] - }, - { - "cell_type": "markdown", - "id": "f7333413-63ba-41f6-bf6c-8f2046402931", - "metadata": {}, - "source": [ - "While it is a valid operation to pass an empty list to the `chunks` keyword argument, this technique does not specify how to chunk the data, and therefore the resulting Dask Array contains only one chunk.\n", - "\n", - "Correct usage of the `chunks` keyword argument specifies how many values in each dimension are contained in a single chunk. In this example, specifying the chunks keyword argument as `chunks={'time':90}` indicates to Xarray and Dask that 90 time slices are allocated to each chunk on the temporal axis.\n", - "\n", - "Since this dataset contains 180 total time slices, the data variable `tos` (holding the sea surface temperature data) is now split into two chunks in the temporal dimension." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e7ac6b2-500f-4371-98ca-dc28dfe27648", - "metadata": {}, - "outputs": [], - "source": [ - "ds = xr.open_dataset(\n", - " DATASETS.fetch('CESM2_sst_data.nc'),\n", - " engine=\"netcdf4\",\n", - " chunks={\"time\": 90, \"lat\": 180, \"lon\": 360},\n", - ")\n", - "ds.tos" - ] - }, - { - "cell_type": "markdown", - "id": "8e175e4d-9950-48fb-b81c-4e67fa00b106", - "metadata": {}, - "source": [ - "It is fairly straightforward to retrieve a list of the chunks and their sizes for each dimension; simply call the `.chunks` method on an Xarray `DataArray`. In this example, we show that the `tos` `DataArray` now contains two chunks on the `time` dimension, with each chunk containing 90 time slices." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4265a298-275c-4f93-992f-6fe8de9a311a", - "metadata": {}, - "outputs": [], - "source": [ - "ds.tos.chunks" - ] - }, - { - "cell_type": "markdown", - "id": "54853249-beec-4d74-8881-82d9d253c3b7", - "metadata": {}, - "source": [ - "### Xarray data structures are first-class dask collections\n", - "\n", - "If an Xarray `Dataset` or `DataArray` object uses a Dask Array, rather than a NumPy array, it counts as a first-class Dask collection. This means that you can pass such an object to `dask.visualize()` and `dask.compute()`, in the same way as an individual Dask Array.\n", - "\n", - "In this example, we call `dask.visualize` on our Xarray `DataArray`, displaying a Dask graph for the `DataArray` object:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "333de4bb-db42-42e8-95f9-5db108962ae7", - "metadata": {}, - "outputs": [], - "source": [ - "dask.visualize(ds)" - ] - }, - { - "cell_type": "markdown", - "id": "d827f1b1-7d13-4707-93c2-45c2f99a7e60", - "metadata": {}, - "source": [ - "### Parallel and lazy computation using `dask.array` with Xarray\n", - "\n", - "\n", - "As described above, Xarray `Datasets` and `DataArrays` containing Dask Arrays are first-class Dask collections. Therefore, computations performed on such objects are deferred until a compute method is called. (This is the definition of lazy computation.)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6cc7961-d43e-4dca-84f2-d3f82631e1f0", - "metadata": {}, - "outputs": [], - "source": [ - "z = ds.tos.mean(['lat', 'lon']).dot(ds.tos.T)\n", - "z" - ] - }, - { - "cell_type": "markdown", - "id": "764537d7-b8e2-4f92-9b5e-cf81a1d8baa7", - "metadata": {}, - "source": [ - "As shown in the above example, the result of the applied operations is an Xarray `DataArray` that contains a Dask Array, an identical object type to the object that the operations were performed on. This is true for any operations that can be applied to Xarray `DataArrays`, including subsetting operations; this next example illustrates this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "631fb768-f900-448a-a605-a93bea26bdc8", - "metadata": {}, - "outputs": [], - "source": [ - "z.isel(lat=0)" - ] - }, - { - "cell_type": "markdown", - "id": "34679cf2-4907-44d7-b386-0c3cbc7ce6d2", - "metadata": {}, - "source": [ - "Because the data subset created above is also a first-class Dask collection, we can view its Dask graph using the `dask.visualize()` function, as shown in this example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ebf0c9e-ac35-4841-9294-2d9e1b6dbc24", - "metadata": {}, - "outputs": [], - "source": [ - "dask.visualize(z)" - ] - }, - { - "cell_type": "markdown", - "id": "56bf9674-57f2-4b49-a5d5-95a4eafac587", - "metadata": {}, - "source": [ - "Since this object is a first-class Dask collection, the computations performed on it have been deferred. To run these computations, we must call a compute method, in this case `.compute()`. This example also uses a progress bar to track the computation progress." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30082ac8-694a-4a48-bf03-840720aaa9b7", - "metadata": {}, - "outputs": [], - "source": [ - "with ProgressBar():\n", - " computed_ds = z.compute()" - ] - }, - { - "cell_type": "markdown", - "id": "7cf5002f-ed03-4318-935a-b5ce6e57434e", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "f58e6da5-1492-4778-8821-aa03721e3db4", - "metadata": {}, - "source": [ - "## Summary\n", - "\n", - "This tutorial covered the use of Xarray to access Dask Arrays, and the use of the `chunks` keyword argument to open datasets with Dask data instead of NumPy data. Another important concept introduced in this tutorial is the usage of Xarray `Datasets` and `DataArrays` as Dask collections, allowing Xarray objects to be manipulated in a similar manner to Dask Arrays. Finally, the concepts of larger-than-memory datasets, lazy computation, and parallel computation, and how they relate to Xarray and Dask, were covered.\n", - "\n", - "### Dask Shortcomings\n", - "\n", - "Although Dask Arrays and NumPy arrays are generally interchangeable, NumPy offers some functionality that is lacking in Dask Arrays. The usage of Dask Array comes with the following relevant issues:\n", - "\n", - "1. Operations where the resulting shape depends on the array values can produce erratic behavior, or fail altogether, when used on a Dask Array. If the operation succeeds, the resulting Dask Array will have unknown chunk sizes, which can cause other sections of code to fail.\n", - "2. Operations that are by nature difficult to parallelize or less useful on very large datasets, such as `sort`, are not included in the Dask Array interface. Some of these operations have supported versions that are inherently more intuitive to parallelize, such as [`topk`](https://pytorch.org/docs/stable/generated/torch.topk.html).\n", - "3. Development of new Dask functionality is only initiated when such functionality is required; therefore, some lesser-used NumPy functions, such as `np.sometrue`, are not yet implemented in Dask. However, many of these functions can be added as community contributions, or have already been added in this manner.\n", - "\n", - "## Learn More\n", - "\n", - "For more in-depth information on Dask Arrays, visit the [official documentation page](https://docs.dask.org/en/latest/array.html). In addition, [this screencast](https://youtu.be/9h_61hXCDuI) reinforces the concepts covered in this tutorial. (If you are viewing this page as a Jupyter Notebook, the screencast will appear below as an embedded YouTube video.)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "21fbe02b-6bee-447b-bbc8-2ba8a0b96c87", - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import YouTubeVideo\n", - "\n", - "YouTubeVideo(id=\"9h_61hXCDuI\", width=600, height=300)" - ] - }, - { - "cell_type": "markdown", - "id": "c282d878-a11f-41a2-9737-caee406ad5c3", - "metadata": {}, - "source": [ - "## Resources and references\n", - "\n", - "* To find specific reference information about Dask and Xarray, see the official documentation pages listed below:\n", - " * [Dask Docs](https://dask.org/)\n", - " * [Dask Examples](https://examples.dask.org/)\n", - " * [Dask Code](https://github.com/dask/dask/)\n", - " * [Dask Blog](https://blog.dask.org/)\n", - " \n", - " * [Xarray Docs](https://xarray.pydata.org/)\n", - " \n", - "* If you require assistance with a specific issue involving Xarray or Dask, the following links may be of use:\n", - " * [`dask`](http://stackoverflow.com/questions/tagged/dask) tag on Stack Overflow, for usage questions\n", - " * [github discussions: dask](https://github.com/dask/dask/discussions) for general, non-bug, discussion, and usage questions\n", - " * [github issues: dask](https://github.com/dask/dask/issues/new) for bug reports and feature requests\n", - " * [github discussions: xarray](https://github.com/pydata/xarray/discussions) for general, non-bug, discussion, and usage questions\n", - " * [github issues: xarray](https://github.com/pydata/xarray/issues/new) for bug reports and feature requests\n", - " \n", - "* Certain sections of this tutorial are adapted from the following existing tutorials:\n", - " * [Dask Array Tutorial](https://tutorial.dask.org/02_array.html)\n", - " * [Parallel Computing with Xarray and Dask](https://tutorial.xarray.dev/intermediate/xarray_and_dask.html)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d7013ef8-33c4-4bd8-8acc-63cce238acb2", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - }, - "vscode": { - "interpreter": { - "hash": "b0fa6594d8f4cbf19f97940f81e996739fb7646882a419484c72d19e05852a7e" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_preview/419/_sources/core/xarray/enso-xarray.ipynb b/_preview/419/_sources/core/xarray/enso-xarray.ipynb deleted file mode 100644 index a419fd5f3..000000000 --- a/_preview/419/_sources/core/xarray/enso-xarray.ipynb +++ /dev/null @@ -1,405 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "# Calculating ENSO with Xarray\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "\n", - "In this tutorial, we perform and demonstrate the following tasks:\n", - "\n", - "1. Load SST data from the CESM2 model\n", - "2. Mask data using `.where()`\n", - "3. Compute climatologies and anomalies using `.groupby()`\n", - "4. Use `.rolling()` to compute moving average\n", - "5. Compute, normalize, and plot the Niño 3.4 Index" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Introduction to Xarray](xarray-intro) | Necessary | |\n", - "| [Computation and Masking](computation-masking) | Necessary | |\n", - "\n", - "\n", - "\n", - "- **Time to learn**: 20 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "## Imports \n", - "\n", - "For this tutorial, we import several Python packages. As plotting ENSO data requires a geographically accurate map, Cartopy is imported to handle geographic features and map projections. Xarray is used to manage raw data, and Matplotlib allows for feature-rich data plotting. Finally, a custom Pythia package is imported, in this case allowing access to the Pythia example data library." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import cartopy.crs as ccrs\n", - "import matplotlib.pyplot as plt\n", - "import xarray as xr\n", - "from pythia_datasets import DATASETS" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "## The Niño 3.4 Index" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "In this tutorial, we combine topics covered in previous Xarray tutorials to demonstrate a real-world example. The real-world scenario demonstrated in this tutorial is the computation of the [Niño 3.4 Index](https://climatedataguide.ucar.edu/climate-data/nino-sst-indices-nino-12-3-34-4-oni-and-tni), as shown in the CESM2 submission for the [CMIP6 project](https://esgf-node.llnl.gov/projects/cmip6/). A rough definition of Niño 3.4, in addition to a definition of Niño data computation, is listed below:\n", - "\n", - "> Niño 3.4 (5N-5S, 170W-120W): The Niño 3.4 anomalies may be thought of as representing the average equatorial SSTs across the Pacific from about the dateline to the South American coast. The Niño 3.4 index typically uses a 5-month running mean, and El Niño or La Niña events are defined when the Niño 3.4 SSTs exceed +/- 0.4C for a period of six months or more.\n", - "\n", - "> Niño X Index computation: a) Compute area averaged total SST from Niño X region; b) Compute monthly climatology (e.g., 1950-1979) for area averaged total SST from Niño X region, and subtract climatology from area averaged total SST time series to obtain anomalies; c) Smooth the anomalies with a 5-month running mean; d) Normalize the smoothed values by its standard deviation over the climatological period.\n", - "\n", - "![](https://www.ncdc.noaa.gov/monitoring-content/teleconnections/nino-regions.gif)\n", - "\n", - "The overall goal of this tutorial is to produce a plot of ENSO data using Xarray; this plot will resemble the Oceanic Niño Index plot shown below.\n", - "\n", - "![ONI index plot from NCAR Climate Data Guide](https://climatedataguide.ucar.edu/sites/default/files/styles/extra_large/public/2022-03/indices_oni_2_2_lg.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "In this first example, we begin by opening datasets containing the sea-surface temperature (SST) and grid-cell size data. (These datasets are taken from the Pythia example data library, using the Pythia package imported above.) The two datasets are then combined into a single dataset using Xarray's `merge` method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filepath = DATASETS.fetch('CESM2_sst_data.nc')\n", - "data = xr.open_dataset(filepath)\n", - "filepath2 = DATASETS.fetch('CESM2_grid_variables.nc')\n", - "areacello = xr.open_dataset(filepath2).areacello\n", - "\n", - "ds = xr.merge([data, areacello])\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example uses Matplotlib and Cartopy to plot the first time slice of the dataset on an actual geographic map. By doing so, we verify that the data values fit the pattern of SST data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(12, 6))\n", - "ax = plt.axes(projection=ccrs.Robinson(central_longitude=180))\n", - "ax.coastlines()\n", - "ax.gridlines()\n", - "ds.tos.isel(time=0).plot(\n", - " ax=ax, transform=ccrs.PlateCarree(), vmin=-2, vmax=30, cmap='coolwarm'\n", - ");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Select the Niño 3.4 region \n", - "\n", - "In this set of examples, we demonstrate the selection of data values from a dataset which are located in the Niño 3.4 geographic region. The following example illustrates a selection technique that uses the `sel()` or `isel()` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tos_nino34 = ds.sel(lat=slice(-5, 5), lon=slice(190, 240))\n", - "tos_nino34" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example illustrates the alternate technique for selecting Niño 3.4 data, which makes use of the `where()` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tos_nino34 = ds.where(\n", - " (ds.lat < 5) & (ds.lat > -5) & (ds.lon > 190) & (ds.lon < 240), drop=True\n", - ")\n", - "tos_nino34" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we plot the selected region to ensure it fits the definition of the Niño 3.4 region:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(12, 6))\n", - "ax = plt.axes(projection=ccrs.Robinson(central_longitude=180))\n", - "ax.coastlines()\n", - "ax.gridlines()\n", - "tos_nino34.tos.isel(time=0).plot(\n", - " ax=ax, transform=ccrs.PlateCarree(), vmin=-2, vmax=30, cmap='coolwarm'\n", - ")\n", - "ax.set_extent((120, 300, 10, -10))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Compute the anomalies\n", - "\n", - "There are three main steps to obtain the anomalies from the Niño 3.4 dataset created in the previous set of examples. First, we use the `groupby()` method to convert to monthly data. Second, we subtract the mean sea-surface temperature (SST) from the monthly data. Finally, we obtain the anomalies by computing a weighted average. These steps are illustrated in the next example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gb = tos_nino34.tos.groupby('time.month')\n", - "tos_nino34_anom = gb - gb.mean(dim='time')\n", - "index_nino34 = tos_nino34_anom.weighted(tos_nino34.areacello).mean(dim=['lat', 'lon'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this example, we smooth the data curve by applying a `mean` function with a 5-month moving window to the anomaly dataset. We then plot the smoothed data against the original data to demonstrate:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "index_nino34_rolling_mean = index_nino34.rolling(time=5, center=True).mean()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "index_nino34.plot(size=8)\n", - "index_nino34_rolling_mean.plot()\n", - "plt.legend(['anomaly', '5-month running mean anomaly'])\n", - "plt.title('SST anomaly over the Niño 3.4 region');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since the ENSO index conveys deviations from a norm, the calculation of Niño data requires a standard deviation. In this example, we calculate the standard deviation of the SST in the Niño 3.4 region data, across the entire time period of the data array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "std_dev = tos_nino34.tos.std()\n", - "std_dev" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The final step of the Niño 3.4 index calculation involves normalizing the data. In this example, we perform this normalization by dividing the smoothed anomaly data by the standard deviation calculated above:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "normalized_index_nino34_rolling_mean = index_nino34_rolling_mean / std_dev" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize the computed Niño 3.4 index" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this example, we use Matplotlib to generate a plot of our final Niño 3.4 data. This plot is set up to highlight values above 0.5, corresponding to El Niño (warm) events, and values below -0.5, corresponding to La Niña (cold) events." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = plt.figure(figsize=(12, 6))\n", - "\n", - "plt.fill_between(\n", - " normalized_index_nino34_rolling_mean.time.data,\n", - " normalized_index_nino34_rolling_mean.where(\n", - " normalized_index_nino34_rolling_mean >= 0.4\n", - " ).data,\n", - " 0.4,\n", - " color='red',\n", - " alpha=0.9,\n", - ")\n", - "plt.fill_between(\n", - " normalized_index_nino34_rolling_mean.time.data,\n", - " normalized_index_nino34_rolling_mean.where(\n", - " normalized_index_nino34_rolling_mean <= -0.4\n", - " ).data,\n", - " -0.4,\n", - " color='blue',\n", - " alpha=0.9,\n", - ")\n", - "\n", - "normalized_index_nino34_rolling_mean.plot(color='black')\n", - "plt.axhline(0, color='black', lw=0.5)\n", - "plt.axhline(0.4, color='black', linewidth=0.5, linestyle='dotted')\n", - "plt.axhline(-0.4, color='black', linewidth=0.5, linestyle='dotted')\n", - "plt.title('Niño 3.4 Index');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "\n", - "This tutorial covered the use of Xarray features, including selection, grouping, and statistical functions, to compute and visualize a data index important to climate science." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and References\n", - "\n", - "- [Niño 3.4 index](https://climatedataguide.ucar.edu/climate-data/nino-sst-indices-nino-12-3-34-4-oni-and-tni)\n", - "- [Matplotlib's `fill_between` method](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.fill_between.html)\n", - "- [Matplotlib's `axhline` method](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.axhline.html) (see also its analogous `axvline` method)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - }, - "toc-autonumbering": false, - "toc-showcode": false, - "toc-showmarkdowntxt": false, - "toc-showtags": false - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/core/xarray/xarray-intro.ipynb b/_preview/419/_sources/core/xarray/xarray-intro.ipynb deleted file mode 100644 index 4e3033659..000000000 --- a/_preview/419/_sources/core/xarray/xarray-intro.ipynb +++ /dev/null @@ -1,938 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![xarray Logo](http://xarray.pydata.org/en/stable/_static/dataset-diagram-logo.png \"xarray Logo\")\n", - "\n", - "# Introduction to Xarray" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overview\n", - "\n", - "The examples in this tutorial focus on the fundamentals of working with gridded, labeled data using Xarray. Xarray works by introducing additional abstractions into otherwise ordinary data arrays. In this tutorial, we demonstrate the usefulness of these abstractions. The examples in this tutorial explain how the proper usage of Xarray abstractions generally leads to simpler, more robust code.\n", - "\n", - "The following topics will be covered in this tutorial:\n", - "\n", - "1. Create a `DataArray`, one of the core object types in Xarray\n", - "1. Understand how to use named coordinates and metadata in a `DataArray`\n", - "1. Combine individual `DataArrays` into a `Dataset`, the other core object type in Xarray\n", - "1. Subset, slice, and interpolate the data using named coordinates\n", - "1. Open netCDF data using Xarray\n", - "1. Basic subsetting and aggregation of a `Dataset`\n", - "1. Brief introduction to plotting with Xarray" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [NumPy Basics](../numpy/numpy-basics) | Necessary | |\n", - "| [Intermediate NumPy](../numpy/intermediate-numpy) | Helpful | Familiarity with indexing and slicing arrays |\n", - "| [NumPy Broadcasting](../numpy/numpy-broadcasting) | Helpful | Familiarity with array arithmetic and broadcasting |\n", - "| [Introduction to Pandas](../pandas/pandas) | Helpful | Familiarity with labeled data |\n", - "| [Datetime](../datetime/datetime) | Helpful | Familiarity with time formats and the `timedelta` object |\n", - "| [Understanding of NetCDF](some-link-to-external-resource) | Helpful | Familiarity with metadata structure |\n", - "\n", - "- **Time to learn**: 40 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Imports" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In earlier tutorials, we explained the abbreviation of commonly used scientific Python package names in import statements. Just as `numpy` is abbreviated `np`, and just as `pandas` is abbreviated `pd`, the name `xarray` is often abbreviated `xr` in import statements. In addition, we also import `pythia_datasets`, which provides sample data used in these examples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import timedelta\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "import xarray as xr\n", - "from pythia_datasets import DATASETS" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introducing the `DataArray` and `Dataset`\n", - "\n", - "As stated in earlier tutorials, NumPy arrays contain many useful features, making NumPy an essential part of the scientific Python stack. Xarray expands on these features, adding streamlined data manipulation capabilities. These capabilities are similar to those provided by Pandas, except that they are focused on gridded N-dimensional data instead of tabular data. Its interface is based largely on the netCDF data model (variables, attributes, and dimensions), but it goes beyond the traditional netCDF interfaces in order to provide additional useful functionality, similar to netCDF-java's [Common Data Model (CDM)](https://docs.unidata.ucar.edu/netcdf-java/current/userguide/common_data_model_overview.html). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creation of a `DataArray` object\n", - "\n", - "The `DataArray` in one of the most basic elements of Xarray; a `DataArray` object is similar to a numpy `ndarray` object. (For more information, see the documentation [here](http://xarray.pydata.org/en/stable/user-guide/data-structures.html#dataarray).) In addition to retaining most functionality from NumPy arrays, Xarray `DataArrays` provide two critical pieces of functionality:\n", - "\n", - "1. Coordinate names and values are stored with the data, making slicing and indexing much more powerful.\n", - "2. Attributes, similar to those in netCDF files, can be stored in a container built into the `DataArray`.\n", - "\n", - "In these examples, we create a NumPy array, and use it as a wrapper for a new `DataArray` object; we then explore some properties of a `DataArray`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Generate a random numpy array\n", - "\n", - "In this first example, we create a numpy array, holding random placeholder data of temperatures in Kelvin:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = 283 + 5 * np.random.randn(5, 3, 4)\n", - "data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Wrap the array: first attempt\n", - "\n", - "For our first attempt at wrapping a NumPy array into a `DataArray`, we simply use the `DataArray` method of Xarray, passing the NumPy array we just created:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp = xr.DataArray(data)\n", - "temp" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note two things:\n", - "\n", - "1. Since NumPy arrays have no dimension names, our new `DataArray` takes on placeholder dimension names, in this case `dim_0`, `dim_1`, and `dim_2`. In our next example, we demonstrate how to add more meaningful dimension names.\n", - "2. If you are viewing this page as a Jupyter Notebook, running the above example generates a rich display of the data contained in our `DataArray`. This display comes with many ways to explore the data; for example, clicking the array symbol expands or collapses the data view." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Assign dimension names\n", - "\n", - "Much of the power of Xarray comes from making use of named dimensions. In order to make full use of this, we need to provide more useful dimension names. We can generate these names when creating a `DataArray` by passing an ordered list of names to the `DataArray` method, using the keyword argument `dims`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp = xr.DataArray(data, dims=['time', 'lat', 'lon'])\n", - "temp" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This `DataArray` is already an improvement over a NumPy array; the `DataArray` contains names for each of the dimensions (or axes in NumPy parlance). An additional improvement is the association of coordinate-value arrays with data upon creation of a `DataArray`. In the next example, we illustrate the creation of NumPy arrays representing the coordinate values for each dimension of the `DataArray`, and how to associate these coordinate arrays with the data in our `DataArray`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a `DataArray` with named Coordinates\n", - "\n", - "#### Make time and space coordinates\n", - "\n", - "In this example, we use [Pandas](../pandas) to create an array of [datetime data](../datetime). This array will be used in a later example to add a named coordinate, called `time`, to a `DataArray`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "times = pd.date_range('2018-01-01', periods=5)\n", - "times" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Before associating coordinates with our `DataArray`, we must also create latitude and longitude coordinate arrays. In these examples, we use placeholder data, and create the arrays in NumPy format:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lons = np.linspace(-120, -60, 4)\n", - "lats = np.linspace(25, 55, 3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Initialize the `DataArray` with complete coordinate info\n", - "\n", - "In this example, we create a new `DataArray`. Similar to an earlier example, we use the `dims` keyword argument to specify the dimension names; however, in this case, we also specify the coordinate arrays using the `coords` keyword argument:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp = xr.DataArray(data, coords=[times, lats, lons], dims=['time', 'lat', 'lon'])\n", - "temp" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Set useful attributes\n", - "\n", - "As described above, `DataArrays` have a built-in container for attribute metadata. These attributes are similar to those in netCDF files, and are added to a `DataArray` using its `attrs` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.attrs['units'] = 'kelvin'\n", - "temp.attrs['standard_name'] = 'air_temperature'\n", - "\n", - "temp" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Issues with preservation of attributes\n", - "\n", - "In this example, we illustrate an important concept relating to attributes. When a mathematical operation is performed on a `DataArray`, all of the coordinate arrays remain attached to the `DataArray`, but any attribute metadata assigned is lost. Attributes are removed in this way due to the fact that they may not convey correct or appropriate metadata after an arbitrary arithmetic operation.\n", - "\n", - "This example converts our DataArray values from Kelvin to degrees Celsius. Pay attention to the attributes in the Jupyter rich display below. (If you are not viewing this page as a Jupyter notebook, see the Xarray documentation to learn how to display the attributes.)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp_in_celsius = temp - 273.15\n", - "temp_in_celsius" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In addition, if you need more details on how Xarray handles metadata, you can review this [documentation page](http://xarray.pydata.org/en/stable/getting-started-guide/faq.html#approach-to-metadata)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### The `Dataset`: a container for `DataArray`s with shared coordinates\n", - "\n", - "Along with the `DataArray`, the other main object type in Xarray is the `Dataset`. `Datasets` are containers similar to Python dictionaries; each `Dataset` can hold one or more `DataArrays`. In addition, the `DataArrays` contained in a `Dataset` can share coordinates, although this behavior is optional. (For more information, see the [official documentation page](http://xarray.pydata.org/en/stable/user-guide/data-structures.html#dataset).)\n", - "\n", - "`Dataset` objects are most often created by loading data from a data file. We will cover this functionality in a later example; in this example, we will create a `Dataset` from two `DataArrays`. We will use our existing temperature `DataArray` for one of these `DataArrays`; the other one is created in the next example.\n", - "\n", - "In addition, both of these `DataArrays` will share coordinate axes. Therefore, the next example will also illustrate the usage of common coordinate axes across `DataArrays` in a `Dataset`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create a pressure `DataArray` using the same coordinates\n", - "\n", - "In this example, we create a `DataArray` object to hold pressure data. This new `DataArray` is set up in a very similar fashion to the temperature `DataArray` created above." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pressure_data = 1000.0 + 5 * np.random.randn(5, 3, 4)\n", - "pressure = xr.DataArray(\n", - " pressure_data, coords=[times, lats, lons], dims=['time', 'lat', 'lon']\n", - ")\n", - "pressure.attrs['units'] = 'hPa'\n", - "pressure.attrs['standard_name'] = 'air_pressure'\n", - "\n", - "pressure" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Create a `Dataset` object\n", - "\n", - "Before we can create a `Dataset` object, we must first name each of the `DataArray` objects that will be added to the new `Dataset`.\n", - "\n", - "To name the `DataArrays` that will be added to our `Dataset`, we can set up a Python dictionary as shown in the next example. We can then pass this dictionary to the `Dataset` method using the keyword argument `data_vars`; this creates a new `Dataset` containing both of our `DataArrays`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds = xr.Dataset(data_vars={'Temperature': temp, 'Pressure': pressure})\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As listed in the rich display above, the new `Dataset` object is aware that both `DataArrays` share the same coordinate axes. (Please note that if this page is not run as a Jupyter Notebook, the rich display may be unavailable.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Access Data variables and Coordinates in a `Dataset`\n", - "\n", - "This set of examples illustrates different methods for retrieving `DataArrays` from a `Dataset`.\n", - "\n", - "This first example shows how to retrieve `DataArrays` using the \"dot\" notation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds.Pressure" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In addition, you can access `DataArrays` through a dictionary syntax, as shown in this example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds['Pressure']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Dataset` objects are mainly used for loading data from files, which will be covered later in this tutorial." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Subsetting and selection by coordinate values\n", - "\n", - "Much of the power of labeled coordinates comes from the ability to select data based on coordinate names and values instead of array indices. This functionality will be covered on a basic level in these examples. (Later tutorials will cover this topic in much greater detail.)\n", - "\n", - "### NumPy-like selection\n", - "\n", - "In these examples, we are trying to extract all of our spatial data for a single date; in this case, January 2, 2018. For our first example, we retrieve spatial data using index selection, as with a NumPy array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "indexed_selection = temp[1, :, :] # Index 1 along axis 0 is the time slice we want...\n", - "indexed_selection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example reveals one of the major shortcomings of index selection. In order to retrieve the correct data using index selection, anyone using a `DataArray` must have precise knowledge of the axes in the `DataArray`, including the order of the axes and the meaning of their indices.\n", - "\n", - "By using named coordinates, as shown in the next set of examples, we can avoid this cumbersome burden." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Selecting with `.sel()`\n", - "\n", - "In this example, we show how to select data based on coordinate values, by way of the `.sel()` method. This method takes one or more named coordinates in keyword-argument format, and returns data matching the coordinates." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "named_selection = temp.sel(time='2018-01-02')\n", - "named_selection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This method yields the same result as the index selection, however:\n", - "- we didn't have to know anything about how the array was created or stored\n", - "- our code is agnostic about how many dimensions we are dealing with\n", - "- the intended meaning of our code is much clearer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Approximate selection and interpolation\n", - "\n", - "When working with temporal and spatial data, it is a common practice to sample data close to the coordinate points in a dataset. The following set of examples illustrates some common techniques for this practice.\n", - "\n", - "#### Nearest-neighbor sampling\n", - "\n", - "In this example, we are trying to sample a temporal data point within 2 days of the date `2018-01-07`. Since the final date on our `DataArray`'s temporal axis is `2018-01-05`, this is an appropriate problem.\n", - "\n", - "We can use the `.sel()` method to perform nearest-neighbor sampling, by setting the `method` keyword argument to 'nearest'. We can also optionally provide a `tolerance` argument; with temporal data, this is a `timedelta` object." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.sel(time='2018-01-07', method='nearest', tolerance=timedelta(days=2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using the rich display above, we can see that `.sel` indeed returned the data at the temporal value corresponding to the date `2018-01-05`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Interpolation\n", - "\n", - "In this example, we are trying to extract a timeseries for Boulder, CO, which is located at 40°N latitude and 105°W longitude. Our `DataArray` does not contain a longitude data value of -105, so in order to retrieve this timeseries, we must interpolate between data points.\n", - "\n", - "The `.interp()` method allows us to retrieve data from any latitude and longitude by means of interpolation. This method uses coordinate-value selection, similarly to `.sel()`. (For more information on the `.interp()` method, see the official documentation [here](http://xarray.pydata.org/en/stable/interpolation.html).)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.interp(lon=-105, lat=40)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " In order to interpolate data using Xarray, the SciPy package must be imported. You can learn more about SciPy from the official documentation.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Slicing along coordinates\n", - "\n", - "Frequently, it is useful to select a range, or _slice_, of data along one or more coordinates. In order to understand this process, you must first understand Python `slice` objects. If you are unfamiliar with `slice` objects, you should first read the official [Python slice documentation](https://docs.python.org/3/library/functions.html#slice). Once you are proficient using `slice` objects, you can create slices of data by passing `slice` objects to the `.sel` method, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.sel(\n", - " time=slice('2018-01-01', '2018-01-03'), lon=slice(-110, -70), lat=slice(25, 45)\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " As detailed in the documentation page linked above, the slice function uses the argument order (start, stop[, step]), where step is optional.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Because we are now working with a slice of data, instead of our full dataset, the lengths of our coordinate axes have been shortened, as shown in the Jupyter rich display above. (You may need to use a different display technique if you are not running this page as a Jupyter Notebook.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### One more selection method: `.loc`\n", - "\n", - "In addition to using the `sel()` method to select data from a `DataArray`, you can also use the `.loc` attribute. Every `DataArray` has a `.loc` attribute; in order to leverage this attribute to select data, you can specify a coordinate value in square brackets, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.loc['2018-01-02']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This selection technique is similar to NumPy's index-based selection, as shown below:\n", - "```\n", - "temp[1,:,:]\n", - "```\n", - "However, this technique also resembles the `.sel()` method's fully label-based selection functionality. The advantages and disadvantages of using the `.loc` attribute are discussed in detail below.\n", - "\n", - "This example illustrates a significant disadvantage of using the `.loc` attribute. Namely, we specify the values for each coordinate, but cannot specify the dimension names; therefore, the dimensions must be specified in the correct order, and this order must already be known:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.loc['2018-01-01':'2018-01-03', 25:45, -110:-70]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In contrast with the previous example, this example shows a useful advantage of using the `.loc` attribute. When using the `.loc` attribute, you can specify data slices using a syntax similar to NumPy in addition to, or instead of, using the slice function. Both of these slicing techniques are illustrated below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temp.loc['2018-01-01':'2018-01-03', slice(25, 45), -110:-70]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As described above, the arguments to `.loc` must be in the order of the `DataArray`'s dimensions. Attempting to slice data without ordering arguments properly can cause errors, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This will generate an error\n", - "# temp.loc[-110:-70, 25:45,'2018-01-01':'2018-01-03']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Opening netCDF data\n", - "\n", - "Xarray has close ties to the netCDF data format; as such, netCDF was chosen as the premier data file format for Xarray. Hence, Xarray can easily open netCDF datasets, provided they conform to certain limitations (for example, 1-dimensional coordinates).\n", - "\n", - "### Access netCDF data with `xr.open_dataset`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " The data file for this example, NARR_19930313_0000.nc, is retrieved from Project Pythia's custom example data library. The DATASETS class imported at the top of this page contains a .fetch() method, which retrieves, downloads, and caches a Pythia example data file.\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filepath = DATASETS.fetch('NARR_19930313_0000.nc')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once we have a valid path to a data file that Xarray knows how to read, we can open the data file and load it into Xarray; this is done by passing the path to Xarray's `open_dataset` method, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds = xr.open_dataset(filepath)\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Subsetting the `Dataset`\n", - "\n", - "Xarray's `open_dataset()` method, shown in the previous example, returns a `Dataset` object, which must then be assigned to a variable; in this case, we call the variable `ds`. Once the netCDF dataset is loaded into an Xarray `Dataset`, we can pull individual `DataArrays` out of the `Dataset`, using the technique described earlier in this tutorial. In this example, we retrieve isobaric pressure data, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds.isobaric1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "(As described earlier in this tutorial, we can also use dictionary syntax to select specific `DataArrays`; in this case, we would write `ds['isobaric1']`.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Many of the subsetting operations usable on `DataArrays` can also be used on `Datasets`. However, when used on `Datasets`, these operations are performed on every `DataArray` in the `Dataset`, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds_1000 = ds.sel(isobaric1=1000.0)\n", - "ds_1000" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As shown above, the subsetting operation performed on the `Dataset` returned a new `Dataset`. If only a single `DataArray` is needed from this new `Dataset`, it can be retrieved using the familiar dot notation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds_1000.Temperature_isobaric" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Aggregation operations\n", - "\n", - "As covered earlier in this tutorial, you can use named dimensions in an Xarray `Dataset` to manually slice and index data. However, these dimension names also serve an additional purpose: you can use them to specify dimensions to aggregate on. There are many different aggregation operations available; in this example, we focus on `std` (standard deviation)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "u_winds = ds['u-component_of_wind_isobaric']\n", - "u_winds.std(dim=['x', 'y'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Info

\n", - " Recall from previous tutorials that aggregations in NumPy operate over axes specified by numeric values. However, with Xarray objects, aggregation dimensions are instead specified through a list passed to the dim keyword argument.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this set of examples, we will be using the sample dataset defined above. The calculations performed in these examples compute the mean temperature profile, defined as temperature as a function of pressure, over Colorado. For the purposes of these examples, the bounds of Colorado are defined as follows:\n", - " * x: -182km to 424km\n", - " * y: -1450km to -990km\n", - " \n", - "This dataset uses a Lambert Conformal projection; therefore, the data values shown above are projected to specific latitude and longitude values. In this example, these latitude and longitude values are 37°N to 41°N and 102°W to 109°W. Using the original data values and the `mean` aggregation function as shown below yields the following mean temperature profile data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps = ds.Temperature_isobaric\n", - "co_temps = temps.sel(x=slice(-182, 424), y=slice(-1450, -990))\n", - "prof = co_temps.mean(dim=['x', 'y'])\n", - "prof" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plotting with Xarray\n", - "\n", - "As demonstrated earlier in this tutorial, there are many benefits to storing data as Xarray `DataArrays` and `Datasets`. In this section, we will cover another major benefit: Xarray greatly simplifies plotting of data stored as `DataArrays` and `Datasets`. One advantage of this is that many common plot elements, such as axis labels, are automatically generated and optimized for the data being plotted. The next set of examples demonstrates this and provides a general overview of plotting with Xarray.\n", - "\n", - "### Simple visualization with `.plot()`\n", - "\n", - "Similarly to [Pandas](../pandas/pandas), Xarray includes a built-in plotting interface, which makes use of [Matplotlib](../matplotlib) behind the scenes. In order to use this interface, you can call the `.plot()` method, which is included in every `DataArray`.\n", - "\n", - "In this example, we show how to create a basic plot from a `DataArray`. In this case, we are using the `prof` `DataArray` defined above, which contains a Colorado mean temperature profile." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "prof.plot()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the figure shown above, Xarray has generated a line plot, which uses the mean temperature profile and the `'isobaric'` coordinate variable as axes. In addition, the axis labels and unit information have been read automatically from the `DataArray`'s metadata." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Customizing the plot\n", - "\n", - "As mentioned above, the `.plot()` method of Xarray `DataArrays` uses Matplotlib behind the scenes. Therefore, knowledge of Matplotlib can help you more easily customize plots generated by Xarray.\n", - "\n", - "In this example, we need to customize the air temperature profile plot created above. There are two changes that need to be made:\n", - "- swap the axes, so that the Y (vertical) axis corresponds to isobaric levels\n", - "- invert the Y axis to match the model of air pressure decreasing at higher altitudes\n", - "\n", - "We can make these changes by adding certain keyword arguments when calling `.plot()`, as shown below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "prof.plot(y=\"isobaric1\", yincrease=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plotting 2-D data\n", - "\n", - "In the previous example, we used `.plot()` to generate a plot from 1-D data, and the result was a line plot. In this section, we illustrate plotting of 2-D data.\n", - "\n", - "In this example, we illustrate basic plotting of a 2-D array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "temps.sel(isobaric1=1000).plot()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The figure above is generated by Matplotlib's `pcolormesh` method, which was automatically called by Xarray's `plot` method. This occurred because Xarray recognized that the `DataArray` object calling the `plot` method contained two distinct coordinate variables.\n", - "\n", - "The plot generated by the above example is a map of air temperatures over North America, on the 1000 hPa isobaric surface. If a different map projection or added geographic features are needed on this plot, the plot can easily be modified using [Cartopy](../cartopy)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "\n", - "Xarray expands on Pandas' labeled-data functionality, bringing the usefulness of labeled data operations to N-dimensional data. As such, it has become a central workhorse in the geoscience community for the analysis of gridded datasets. Xarray allows us to open self-describing NetCDF files and make full use of the coordinate axes, labels, units, and other metadata. By making use of labeled coordinates, our code is often easier to write, easier to read, and more robust.\n", - "\n", - "### What's next?\n", - "\n", - "Additional notebooks to appear in this section will describe the following topics in greater detail:\n", - "- performing arithmetic and broadcasting operations with Xarray data structures\n", - "- using \"group by\" operations\n", - "- remote data access with OPeNDAP\n", - "- more advanced visualization, including map integration with Cartopy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resources and references\n", - "\n", - "This tutorial contains content adapted from the material in [Unidata's Python Training](https://unidata.github.io/python-training/workshop/XArray/xarray-and-cf/).\n", - "\n", - "Most basic questions and issues with Xarray can be resolved with help from the material in the [Xarray documentation](http://xarray.pydata.org/en/stable/). Some of the most popular sections of this documentation are listed below:\n", - "- [Why Xarray](http://xarray.pydata.org/en/stable/getting-started-guide/why-xarray.html)\n", - "- [Quick overview](http://xarray.pydata.org/en/stable/getting-started-guide/quick-overview.html#)\n", - "- [Example gallery](http://xarray.pydata.org/en/stable/gallery.html)\n", - "\n", - "Another resource you may find useful is this [Xarray Tutorial collection](https://xarray-contrib.github.io/xarray-tutorial/), created from content hosted on GitHub." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/foundations/conda.md b/_preview/419/_sources/foundations/conda.md deleted file mode 100644 index f371876cb..000000000 --- a/_preview/419/_sources/foundations/conda.md +++ /dev/null @@ -1,134 +0,0 @@ -# Installing and Managing Python with Conda - ---- - -## Overview - -Conda is an open-source, cross-platform, language-agnostic package manager and environment management system that allows you to quickly install, run, and update packages within your work environment(s). - -Here we will cover: - -1. What are packages? -2. Installing Conda -3. Creating a Conda environment -4. Useful Conda commands - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------------------------------------------------------------------- | ---------- | ----- | -| [Installing and Running Python](https://foundations.projectpythia.org/foundations/how-to-run-python.html) | Helpful | | - -- **Time to learn**: 20 minutes - ---- - -## What are Packages? - -A Python package is a collection of modules, which, in turn, are essentially Python scripts that contain published functionality. There are Python packages for data input, data analysis, data visualization, etc. Each package offers a unique toolset and may have its own unique syntax rules. - -Package management is useful because you may want to update a package for one of your projects, but keep it at the same version in other projects to ensure that they continue to run as expected. - -## Installing Conda - -We recommend you install Miniconda. You can do that by following the [instructions for your machine](https://docs.conda.io/en/latest/miniconda.html). - -Miniconda only comes with the `conda` package management system; it is a pared-down version of the full Anaconda Python distribution. - -[Installing Anaconda](https://docs.anaconda.com/anaconda/install/) takes longer and uses up more disk space, but provides you with more functionality, including Spyder (a Python-specific integrated development environment or IDE) and Jupyter, in addition to other immediately installed packages. Also, the interface of Anaconda is great if you are uncomfortable with the terminal. - -We recommend Miniconda for two reasons: - -1. It's quicker and takes up less disk space. -2. It encourages you to install only the packages you need in reproducible isolated environments for specific projects. This is generally a more robust way to work with open source tools. - -Once you have `conda` via the Miniconda installer, the next step is to create an environment and install packages. - -## Creating a Conda Environment - -A Conda environment is an interoperable collection of specific versions of packages or libraries that you install and use for a specific workflow. The Conda package manager takes care of dependencies, so everything works together in a predictable way. One huge advantage of using environments is that any changes you make to one environment will not affect your other environments at all, so you are much less likely to "break" something! - -To create a new Conda environment, type `conda create --name` and the name of your environment in your terminal, and then specify any packages that you would like to have installed. For example, to install a Jupyter-ready environment called `sample_environment`, type - -``` -conda create --name sample_environment python jupyterlab -``` - -Once the environment is created, you need to _activate_ it in the current terminal session (see below). - -It is a good idea to create a new environment for every project. Because Python is open source, new versions of the tools are released frequently. Isolated environments help guarantee that your scripts use the same versions of packages and libraries to ensure they run as expected. Similarly, it is best practice to NOT modify your `base` environment. - -## Useful Conda commands - -Some other Conda commands that you will find useful include: - -- Activating a specific environment - -``` -conda activate sample_environment -``` - -- Deactivating the current environment - -``` -conda deactivate -``` - -- Checking what packages/versions are installed in the current environment - -``` -conda list -``` - -- Installing a new package into the current environment - -``` -conda install somepackage -``` - -- Installing a specific version of a package into the current environment - -``` -conda install somepackage=0.17 -``` - -- Updating all packages in the current environment to the latest versions - -``` -conda update --all -``` - -- Checking what conda environments you have - -``` -conda env list -``` - -- Deleting an environment - -``` -conda env remove --name sample_environment -``` - -You can find lots more information in the [Conda documentation](https://docs.conda.io/en/latest/) or this handy [Conda cheat sheet](https://docs.conda.io/projects/conda/en/latest/_downloads/843d9e0198f2a193a3484886fa28163c/conda-cheatsheet.pdf). - -If you're not a command line user, the Anaconda navigator offers GUI functionality for selecting environments and installing packages. - ---- - -## Summary - -Conda is a package and environment management system that allows you to quickly install, run, and update packages within your work environment(s). This is important for gathering all of the tools necessary for your workflow. Conda can be managed in the command line or in the Anaconda GUI. - -### What's Next? - -- [How to Run Python in the Terminal](terminal.md) -- [How to Run Python in a Jupyter Session](jupyter.md) - -## Resources and References - -- [Linux commands](https://cheatography.com/davechild/cheat-sheets/linux-command-line/) -- [Conda documentation](https://docs.conda.io/en/latest/) -- [Conda cheat sheet](https://docs.conda.io/projects/conda/en/latest/_downloads/843d9e0198f2a193a3484886fa28163c/conda-cheatsheet.pdf) -- [Anaconda](https://docs.anaconda.com/anaconda/install/) -- [Miniconda](https://docs.conda.io/en/latest/miniconda.html) diff --git a/_preview/419/_sources/foundations/getting-started-github.md b/_preview/419/_sources/foundations/getting-started-github.md deleted file mode 100644 index c87e40b9b..000000000 --- a/_preview/419/_sources/foundations/getting-started-github.md +++ /dev/null @@ -1,23 +0,0 @@ -```{image} ../images/GitHub-logo.png -:alt: GitHub Logo -:width: 600px -``` - -# Getting Started with GitHub - -Python and Jupyter are cool technologies, but they only scratch the surface of why you might want to adopt Python for your geoscience workflow. - -This section will introduce GitHub, the de facto standard platform for collaboration and version control used by the open-source Python community. - -We will walk users through these topics: - -- [What is GitHub?](github/what-is-github), and how to create your free account -- [What are GitHub Repositories](github/github-repos), and what are some Python-specific examples? -- [Issues and Discussions](github/github-issues) on GitHub: what they're for and how to participate -- [Cloning and Forking a Repository](github/github-cloning-forking) (and what's the difference?) -- [Detailed GitHub Configuration](github/github-setup-advanced), including how to set up secure permissions and notifications -- [Basic Version Control with _git_](github/basic-git): why you may need it, and how to get started -- [What is a git _Branch_?](github/git-branches) -- [What's a Pull Request](github/github-pull-request), and how do you open one? -- [GitHub Workflows](github/github-workflows), sets of best practices for collaborative work -- [Contributing to Project Pythia via GitHub](github/contribute-to-pythia) diff --git a/_preview/419/_sources/foundations/getting-started-jupyter.ipynb b/_preview/419/_sources/foundations/getting-started-jupyter.ipynb deleted file mode 100644 index 821910630..000000000 --- a/_preview/419/_sources/foundations/getting-started-jupyter.ipynb +++ /dev/null @@ -1,275 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "RYdHzMOHLr1U" - }, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "382TcknGLr1V" - }, - "source": [ - "# Getting Started with Jupyter" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "FppyUssDLr1W" - }, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "R5NbthgvLr1W" - }, - "source": [ - "## Overview\n", - "Project Jupyter is a project and community whose goal is to \"develop open-source software, open-standards, and services for interactive computing across dozens of programming languages\". Jupyter consists of four main components: Jupyter Notebooks, Jupyter Kernels, Jupyter Lab, and Jupyter Hub. Jupyter can be executed locally and remotely.\n", - "\n", - "1. Jupyter Notebooks\n", - "2. Jupyter Kernels\n", - "3. Jupyter Lab\n", - "4. Jupyter Hub\n", - "5. Executing Jupyter" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ZZZEz-GrLr1X" - }, - "source": [ - "## Prerequisites\n", - "\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Installing and Running Python: Python in Jupyter](jupyter) | Helpful | |\n", - "\n", - "- **Time to learn**: 10 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MS5w7x7ELr1Y" - }, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Sm5RbdhALr1b" - }, - "source": [ - "## Jupyter Notebooks\n", - "\n", - "The Jupyter Notebook software is an open-source web application that allows you to create and share Jupyter Notebooks (*.ipynb files). Jupyter Notebooks contain executable code, LaTeX equations, visualizations (e.g., plots, pictures), and narrative text. The code does not have to just be Python, other languages such as Julia or R are supported as well. \n", - "\n", - "Jupyter Notebooks are celebrated for their interactive output that allows movement between code, code output, explanations, and more code - similar to how scientists think and solve problems. Jupyter Notebooks can be thought of as a living, runnable publication and make for a great presentation platform." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WlqwpUr4Lr1e" - }, - "source": [ - "## Jupyter Kernels\n", - "Software engines and their environments (e.g., conda environments) that execute the code contained in Jupyter Notebooks." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "F-sZF8KGLr1f" - }, - "source": [ - "## Jupyter Lab\n", - "\n", - "A popular web application on which users can create and write their Jupyter Notebooks, as well as explore data, install software, etc.\n", - "\n", - "You can find more information on running Jupyter Lab [here](jupyterlab)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EnMNKdOVOSJI" - }, - "source": [ - "## Jupyter Hub\n", - "A web-based platform that authenticates users and launches Jupyter Lab applications for users on remote systems." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "5kfhYv7yOVMh" - }, - "source": [ - "## Executing Jupyter" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "tQvmXjFtUSJa" - }, - "source": [ - "### Local Execution Model\n", - "\n", - "You can launch JupyterLab from a terminal; it will open up in a web browser. The application will then be running in that web browser. When you open a notebook, Jupyter opens a kernel which can be tied to a specific coding language.\n", - "\n", - "To launch the JupyterLab interface in your browser, follow the instructions in [Installing and Running Python: Python in Jupyter](https://foundations.projectpythia.org/foundations/jupyter.html).\n", - "\n", - "![Local Execution Model](../images/local-execution-model.gif)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "1ymyUHsMUSlT" - }, - "source": [ - "### Remote Execution Model\n", - "\n", - "In the remote execution model, you start out in the browser, then navigate to a specific URL that points to a JupyterHub. On JupyterHub, you authenticate on the remote system, and then JupyterLab is launched and redirected back to your browser. The interface appears the same as if you were running Jupyter locally.\n", - "\n", - "![Remote Execution Model](../images/remote-execution-model.gif)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kXqWPdonLr1i" - }, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "cYUI27u_Lr1i" - }, - "source": [ - "## Summary\n", - "\n", - "Jupyter consists of four main components:\n", - "- Jupyter Notebooks (the \"*.ipynb\" files),\n", - "- Jupyter Kernels (the work environment),\n", - "- Jupyter Lab (a popular web application and interface for local execution),\n", - "- and Jupyter Hub (an application and launcher for remote execution).\n", - "\n", - "### What's next?\n", - "\n", - "- [JupyterLab](jupyterlab)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "yndFhI9VLr1i" - }, - "source": [ - "## Resources and references\n", - "\n", - "- [Jupyter Documentation](https://jupyter.org/)\n", - "- [Xdev Python Tutorial Seminar Series - Jupyter Notebooks](https://youtu.be/xSzXvwzFsDU)" - ] - } - ], - "metadata": { - "colab": { - "collapsed_sections": [], - "name": "jupyter.ipynb", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - }, - "nbdime-conflicts": { - "local_diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 0, - "op": "addrange", - "valuelist": [ - "Python 3" - ] - }, - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "display_name", - "op": "patch" - } - ], - "key": "kernelspec", - "op": "patch" - } - ], - "remote_diff": [ - { - "diff": [ - { - "diff": [ - { - "key": 0, - "op": "addrange", - "valuelist": [ - "Python3" - ] - }, - { - "key": 0, - "length": 1, - "op": "removerange" - } - ], - "key": "display_name", - "op": "patch" - } - ], - "key": "kernelspec", - "op": "patch" - } - ] - }, - "toc-autonumbering": false - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/foundations/getting-started-python.md b/_preview/419/_sources/foundations/getting-started-python.md deleted file mode 100644 index cbdd93bd1..000000000 --- a/_preview/419/_sources/foundations/getting-started-python.md +++ /dev/null @@ -1,8 +0,0 @@ -# Getting Started with Python - -**_New Python users, start here!_** - -## Topics - -- [Quickstart: Zero to Python](quickstart): For the impatient among us: run your first Python code in the cloud! -- [Installing and Running Python](how-to-run-python): Detailed instructions for choosing a Python platform and getting up and running on a laptop, including using the conda package manager. diff --git a/_preview/419/_sources/foundations/github/basic-git.md b/_preview/419/_sources/foundations/github/basic-git.md deleted file mode 100644 index 5b1c9a4f1..000000000 --- a/_preview/419/_sources/foundations/github/basic-git.md +++ /dev/null @@ -1,447 +0,0 @@ -```{image} ../../images/Git-Logo-2Color.png -:alt: Git Logo -:width: 400px -``` - -# Basic Version Control with _git_ - -## Overview: - -1. The need for version control -1. Basic git usage -1. Making your first git commit -1. Viewing and comparing across the commit history - -## Prerequisites - -| Concepts | Importance | Notes | -| ---------------------------------------------------------- | ----------- | ---------------------------- | -| [What is GitHub?](what-is-github) | Necessary | GitHub user account required | -| [GitHub Repositories](github-repos) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Cloning and Forking a Repository](github-cloning-forking) | Recommended | | -| [Configuring your GitHub Account](github-setup-advanced) | Recommended | | - -- **Time to learn**: 45 minutes - ---- - -## About version control and git - -### What is version control (and why should we care)? - -[Version Control](https://en.wikipedia.org/wiki/Version_control) refers generally to systems for managing changes to documents or files. Version control systems let us keep track of what changes were made to a file, when they were made, and by whom. If you've ever used "Tracked changes" on a Word document with multiple authors, then you've seen a form of version control in action (though NOT one that is well suited to working with computer code!). - -The need for version control is particularly acute when _working with computer code_, where small changes to the text can have huge impacts on the results of running the code. - -Do you have a directory somewhere on your machine right now with five different versions of a Python script like this? - -``` -analysis_script_OLD.py -analysis_script.py -analysis_script_09122021.py -analysis_script_09122021_edit.py -analysis_script_NEW.py -``` - -A Version Control System (VCS) like git will replace this mess with a _well-ordered and labelled history_ of edits that you can freely browse through, and will greatly simplify collaborating with other people on writing new code. - -### What is git? - -#### Git is not GitHub - -That's the first thing to understand. GitHub is a web-based platform for hosting code and collaborating with other people. On the other hand, **git is a command-line Version Control System (VCS)** that you can download and install. It runs on your local computer as well as under the hood on GitHub. You need to understand something about version control with git in order to use many of GitHub's collaboration features. - -#### A little history and nomenclature - -Git has been around [since the mid-2000s](https://en.wikipedia.org/wiki/Git). It was originally written by Linus Torvalds specifically for use in development of the Linux kernel. Git is [FOSS](https://foundations.projectpythia.org/foundations/github/what-is-github.html#free-and-open-source-software-foss) and comes pre-installed on many Linux and Mac OS systems. - -There are many other VCSs out there. A few that you might encounter in scientific codebases include [Subversion](https://subversion.apache.org), [Mercurial](https://www.mercurial-scm.org), and [CVS](http://cvs.nongnu.org). However, git is overwhelmingly the VCS of choice for open-source projects in the Scientific Python ecosystem these days (as well as among software developers more generally). - -There is no universally agreed-upon meaning of the name "git". From the [git project's own README file](https://github.com/git/git/blob/master/README.md): - -> The name "git" was given by Linus Torvalds when he wrote the very first version. He described the tool as "the stupid content tracker" and the name as (depending on your mood): -> -> - random three-letter combination that is pronounceable, and not actually used by any common UNIX command. The fact that it is a mispronunciation of "get" may or may not be relevant. -> - stupid. contemptible and despicable. simple. Take your pick from the dictionary of slang. -> - "global information tracker": you're in a good mood, and it actually works for you. Angels sing, and a light suddenly fills the room. -> - "goddamn idiotic truckload of sh\*t": when it breaks - -#### Git is a distributed VCS - -Aside from being free and widely deployed, an important distinguishing feature of git is that it is a distributed Version Control System. Essentially this means that every git directory on every computer is a complete independent repository with complete history. - -When we cloned the [`github-sandbox`](https://github.com/ProjectPythia/github-sandbox) repository back in the [Cloning and Forking](github-cloning-forking) section, we not only copied the current repository files but also the entire revision history of the repo. - -In this section we are going to explore basic git usage _on our local computer_. Nothing that we do here is going to affect other copies of the repositories stored elsewhere. _So don't worry about breaking anything!_ - -Later, we will explore how to collaborate on code repositories using GitHub. But in keep in mind the basic idea that _all git repos are equal and independent_! You will have separate copies of repos stored on your local machine and in your GitHub organization. - -Now that we are oriented, let's dive into some basic git usage with the [`github-sandbox`](https://github.com/ProjectPythia/github-sandbox) repository! - -## Inspect a git repository with `git status` - -First, make sure you followed the steps in the [Cloning a repository](github-cloning-forking) lesson to make a clone of the `github-sandbox` repo on your local computer. Navigate to wherever you saved your copy of the repo. - -Now meet your new best friend: - -```bash -git status -``` - -which will always give you information about the current git repo. Try it! You should see something like this: - -```bash -On branch main -Your branch is up to date with 'origin/main'. - -nothing to commit, working tree clean -``` - -**Two really important things here**: - -1. The first line show you the current _branch_ (here called `main`). We'll cover branching in more detail in the [next lesson](git-branches), but basically each branch is a completely independent version with its own history. When we start making changes to files, we'll have to pay attention to which branch we're currently on. -1. The last line `nothing to commit, working tree clean` tells us that we haven't made any changes to files. - -You'll want to use - -```bash -git status -``` - -frequently to keep track of things in your repos. - -## Make some changes - -Version control is all about keeping track of changes made to files. So let's make some changes! - -You may have noticed that the file `sample.txt` in the `github-sandbox` repository contains a typo. Here we're going to fix the error and save it locally. - -### Create a new feature branch - -Before we start editing files, the first thing to do is to _create a new branch_ where we can safely make any changes we want. - -```{tip} -While there's nothing stopping us from making changes directly to the `main` branch, it's often best to avoid this! The reason is that it makes collaboration trickier. See the [lesson on Pull Requests](github-pull-request). -``` - -Let's create and checkout a new branch in one line: - -```bash -git checkout -b fix-typo -``` - -Now try your new best friend again: - -```bash -git status -``` - -You should see something like this: - -```bash -On branch fix-typo -nothing to commit, working tree clean -``` - -This tells us that we have switched over to a new branch called `fix-typo`, but there are not (yet) any changes to the files in the repo. - -### Time to make some changes - -Now do the following: - -- Using your favorite text editor, open the file `github-sandbox/sample.txt`. -- Replace the word `Fxing` with the much more satisfying `Fixing`. -- Save the changes. -- Revisit your new best friend `git status`. It should now show something like this: - -```bash -On branch fix-typo -Changes not staged for commit: - (use "git add ..." to update what will be committed) - (use "git restore ..." to discard changes in working directory) - modified: sample.txt - -no changes added to commit (use "git add" and/or "git commit -a") -``` - -Here `git` is telling us that the file `sample.txt` does _not_ match what's in the repository. - -Of course we know what changed in that file because we just finished editing it. But here's a quick and easy way to see the changes: - -```bash -git diff -``` - -which should show you something like this: - -```bash -diff --git a/sample.txt b/sample.txt -index 4bc074c..edc31c0 100644 ---- a/sample.txt -+++ b/sample.txt -@@ -4,6 +4,6 @@ We can use it to demonstrate making pull requests or raising issues in a GitHub - - One good way to contribute to a project is to make additions and/or edits to documentation! - --Fxing something as simple as a typo is a great way to get started as a contributor! -+Fixing something as simple as a typo is a great way to get started as a contributor! - - Or, consider adding some more content to this file. -``` - -We can see here that `git diff` finds the line(s) where our current file differs from what's in the repo, along with a few lines before and after for context. - -The next step is to add our changes to the "official" history of our repo. This is a two-step process (staging and committing). - -## Stage and commit our changes - -The `commit` is the centerpiece of the git workflow. Each commit is a specific set of changes, additions, and/or deletions of files that gets added to the official history of the repository. - -### Staging - -Before we make a commit, we must first stage our changes. Think of staging simply as "getting ready to commit". The two-step process can help avoid accidentally committing something that wasn't ready. - -To stage our changes, we use `git add` like this: - -```bash -git add sample.txt -``` - -and now our new best friend tells us - -```bash -On branch fix-typo -Changes to be committed: - (use "git restore --staged ..." to unstage) - modified: sample.txt - -``` - -Now we see that all-important line `Changes to be committed`, telling us the contents of our staging area. - -If you made a mistake (e.g., staged the wrong file), you can always unstage using `git restore` as shown in the `git status` output. Nothing is permanent until we commit! - -(And if you accidentally commit the wrong thing? Don't worry, you can always "go back in time" to previous commits -- see below!) - -### Committing - -It's time to make a commitment. We can now permanently add our edit to the history of our `fix-typo` branch by doing this: - -```bash -git commit -m 'Fix the typo' -``` - -```{note} -Every commit should have a "message" that explains briefly what the commit is for. Here we set the commit message with the `-m` flag and chose some descriptive text. Note, it's critical to have those quotes around `'Fix the typo'`. Otherwise the command shell will misinterpret what you are trying to do. -``` - -Now when we do `git status` we see - -```bash -On branch fix-typo -nothing to commit, working tree clean -``` - -And we're back to a clean state! We have now added a new permanent change to the history of our repo (or more specifically, to this _branch_ of the repo). - -## Going back in time - -Each commit is essentially a snapshot in time of the state of the repo. So how can we look back on that history, or revert back to a previous version of a file? - -### Viewing the commit history with `git log` - -A simple way to see this history of the current branch is this: - -```bash -git log -``` - -You'll see something like this: - -```bash -commit 7dca0292467e4bbd73643556f83fd1c52b5c113c (HEAD -> fix-typo) -Author: Brian Rose -Date: Mon Jan 17 11:31:49 2022 -0500 - - Fix the typo - -commit 35fcbd991f911e170df550db58f74a082ba18b50 (origin/main, origin/HEAD, main) -Author: Kevin Tyle -Date: Thu Jan 13 11:29:40 2022 -0500 - - Close docstring quote on sample.py - -commit e56ea58071f150ec00904a50341a672456cbcb8f -Author: Kevin Tyle -Date: Tue Jan 11 14:15:31 2022 -0500 - - Create sample.md - -commit f98d05e312d19a84b74c45402a2904ab94d86e45 -Author: Kevin Tyle -Date: Tue Jan 11 13:58:09 2022 -0500 - - Create sample.py -``` - -which shows the last few commits on this branch, including the commit number, author, timestamp, and commit message. You can page down to see the rest of the history -or just press `Q` to exit `git log`! - -```{note} -Every commit has a unique hexadecimal checksum code like `7dca0292467e4bbd73643556f83fd1c52b5c113c`. Your history will look a little different from the above! -``` - -### Checking out a previous commit - -Let's say you want to retrieve the file `sample.txt` from the previous commit. Two possible reasons why: - -1. You just want to take a quick look at something in the previous commit, but then go back to the current version. That's what we'll do here. -2. Maybe you don't like the most recent commit and want to do some new edits _starting from the previous commit_ -- in effect, undoing the most recent commit and going back in time. The simplest way to do this is to _create a new branch_ starting from the previous commit. We'll cover branches more fully in the next lesson. - -To retrieve the previous commit, just use `git checkout` and the unique number code which you can just copy and paste from the `git log` output: - -```bash -git checkout 35fcbd991f911e170df550db58f74a082ba18b50 -``` - -You may see output that looks like this: - -```bash -Note: switching to '35fcbd991f911e170df550db58f74a082ba18b50'. - -You are in 'detached HEAD' state. You can look around, make experimental -changes and commit them, and you can discard any commits you make in this -state without impacting any branches by switching back to a branch. - -If you want to create a new branch to retain commits you create, you may -do so (now or later) by using -c with the switch command. Example: - - git switch -c - -Or undo this operation with: - - git switch - - -Turn off this advice by setting config variable advice.detachedHead to false - -HEAD is now at 35fcbd9 Close docstring quote on sample.py -``` - -(the details may vary depending on what version of git you are running). - -By `detached HEAD`, git is telling us that we are NOT on the most recent commit in this branch. - -If you inspect `sample.txt` in your editor, you will see that the typo `Fxing` is back! - -As the git message above is reminding us, it's possible to create an entirely new branch with changes that we make from this point in the history using `git switch -c`. But for now, let's just go back to the most recent commit on our `fix-typo` branch: - -```bash -git checkout fix-typo -``` - -## Comparing versions - -We already saw one use of the `git diff` command to look at changes in a repo. By default `git diff` will compare the currently saved files against the most recent commit. - -We can also use `git diff` to compare across commits within a branch, or between two different branches. Here are some examples. - -### Compare across commits - -To compare across any commits in our history, we again use the unique commit checksum that we listed with `git log`: - -```bash -git diff HEAD 35fcbd991f911e170df550db58f74a082ba18b50 -``` - -gives - -```bash -diff --git a/sample.txt b/sample.txt -index edc31c0..4bc074c 100644 ---- a/sample.txt -+++ b/sample.txt -@@ -4,6 +4,6 @@ We can use it to demonstrate making pull requests or raising issues in a GitHub - - One good way to contribute to a project is to make additions and/or edits to documentation! - --Fixing something as simple as a typo is a great way to get started as a contributor! -+Fxing something as simple as a typo is a great way to get started as a contributor! - - Or, consider adding some more content to this file. -``` - -```{note} -Here we use `HEAD` as an alias for the _most recent commit_. -``` - -### Compare across branches - -Recall that, since we have done all our editing in a new branch, the `main` branch still has the typo! - -We can see this with `git diff` using the `..` notation to compare two branches: - -```bash -git diff main..fix-typo -``` - -The output is very similar: - -```bash -diff --git a/sample.txt b/sample.txt -index 4bc074c..edc31c0 100644 ---- a/sample.txt -+++ b/sample.txt -@@ -4,6 +4,6 @@ We can use it to demonstrate making pull requests or raising issues in a GitHub - - One good way to contribute to a project is to make additions and/or edits to documentation! - --Fxing something as simple as a typo is a great way to get started as a contributor! -+Fixing something as simple as a typo is a great way to get started as a contributor! - - Or, consider adding some more content to this file. -``` - -The `git diff` command is a powerful comparison tool (and maybe your second new best friend). For many more detail on its usage, see the [git documentation](https://git-scm.com/docs/git-diff). - -## Git commands mini-reference - -### Commands we used in this tutorial - -- `git status`: see what branch we're on and what state our repo is in. -- `git checkout`: switch between branches (use the `-b` flag to create a new branch and check it out) -- `git diff`: compare files between current version and last commit (default), between two commits, or between two branches. -- `git add`: stage a file for a commit. -- `git commit`: create a new commit with the staged files. -- `git log`: see the commit history of our branch. - -### Some other git commands you'll want to know - -We'll cover many of these in subsequent sections. - -- `git branch`: list all the branch in the repo -- `git mv` and `git rm`: git-enhanced versions of the `mv` (move file) and `rm` (remove file) commands. These will automatically stage the changes in your current branch. -- `git merge`: merge changes from one branch into another. -- `git push` and `git pull`: export or input changes between your local branch and a remote repository (e.g. hosted on GitHub). -- `git init`: create a brand-new repo in the current directory - ---- - -## Summary - -- Version control is an important tool for working with code files (or anything that is saved as plain text). -- git is the most common version control software in use today. -- `git status` is your new best friend because it gives you a quick view into what's going on in a git repository. -- Every branch of a git repository has a history which is a series of numbered and labelled commits. -- You can view this history with `git log` -- Making a new commit is a two-step process with `git add` and `git commit`. -- Commits are non-destructive, meaning you can always go back in time to previous commits. - -### What's Next? - -Next we'll explore the concept of branching in git repositories in more detail, including how to merge changes made on one branch into another branch. - -## References - -1. [Official git documentation](https://git-scm.com/doc) -1. [The Software Carpentries beginner lessons on git](https://swcarpentry.github.io/git-novice/) diff --git a/_preview/419/_sources/foundations/github/contribute-to-pythia.md b/_preview/419/_sources/foundations/github/contribute-to-pythia.md deleted file mode 100644 index 313688227..000000000 --- a/_preview/419/_sources/foundations/github/contribute-to-pythia.md +++ /dev/null @@ -1,97 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# Contribute to Project Pythia via GitHub - -## Overview: - -1. Suggest a change -2. Make the edits -3. Create a Pull Request - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------- | ----------- | ----- | -| [What is GitHub](what-is-github) | Necessary | | -| [GitHub Repositories](github-repos) | Necessary | | -| [Cloning and Forking](github-cloning-forking) | Necessary | | -| [Basic Version Control with _git_](basic-git) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Branches](git-branches) | Necessary | | -| [Pull Requests](github-pull-request) | Necessary | | -| [Reviewing Pull Requests](review-pr) | Recommended | | -| [GitHub Workflows](git-workflow) | Necessary | | - -- **Time to learn**: 30 minutes - ---- - -Now that you have become more familiar with how to use Git and GitHub, you might have an idea or some material that you want to contribute to Project Pythia! The [Project Pythia Contributor's Guide](https://projectpythia.org/contributing.html) describes the steps required to submit a PR to any of Project Pythia's repos. Here, we will go through an example of submitting a PR to `pythia-foundations`. - -## Suggest a change - -One simple way to contribute is to fix a typo or suggest a change to one of the tutorials. For example, in the [Computations and Masks with Xarray tutorial](https://foundations.projectpythia.org/core/xarray/computation-masking.html), let's suggest a clarification that the sea surface temperature is called `tos` in the dataset we are using. - -Computations and Masks with Xarray - -We could open an issue to suggest this change in order to get feedback on the idea before we take the time to edit files, but since this is such a small change, let's just create a PR. - -## Make the edits - -We will follow the [Forking Workflow](https://foundations.projectpythia.org/foundations/github/github-workflows.html#forking-workflow) described in the previous section of this tutorial, assuming `pythia-foundations` has already been forked: - -- Create a new branch with a descriptive name -- Make the changes and commit them locally -- Push to the remote repository -- Open a PR on GitHub - -First, making the new branch, - -```bash -git branch clarify-sst-tos -git checkout clarify-sst-tos -``` - -There are a variety of ways to make changes, depending on the type of file, as well as preference. Here we want to edit a Jupyter Notebook (file extension `.ipynb`), so we can use JupyterLab. We find the file of interest at `/core/xarray/computation-masking.ipynb` and add in some text: - -Notebook in JupyterLab - -After saving and exiting (and checking for changes with a `git status`), we commit with the following: - -```bash -git add core/xarray/computation-masking.ipynb -git commit -m 'Mention that SST is called tos in the model' -``` - -Then pushing to our remote aliased `origin`: - -```bash -git push origin clarify-sst-tos -``` - -## Create a Pull Request - -Now, going to our remote repo on GitHub, forked from `pythia-foundations`, we see that recent changes have been made. By clicking on the "Compare & pull request" button, we can open a PR, proposing that our changes be merged into the main branch of `ProjectPythia/pythia-foundations`. - -GitHub Forked Repo - -Project Pythia has an automated reviewer system: when a PR is created, two members of the organization will be randomly chosen to review it. If your PR is not immediately ready to be approved and merged, open it as a draft to delay the review process. As shown in this [Git Branches section](https://foundations.projectpythia.org/foundations/github/git-branches.html#merging-branches), the "Draft pull request" button is found using the arrow on the "Create pull request" button. - -Let's add the `content` tag and open this one as a draft for now: - -GitHub PR Creation - -For any PR opened in `pythia-foundations`, there will be a few checks that need to pass before merging is allowed. Once the `deploy-book / build` check has completed (which will likely take a few minutes), there will be a Deployment Preview URL commented by the github-actions bot that will take you to a build of the Pythia Foundations book with your edits. There you can ensure your edits show up as expected. - -GitHub Checks - -Once it is ready, click "Ready for review" to take it out of draft mode. Now we wait for any comments or reviews! - ---- - -## Summary - -- You can contribute to Project Pythia by suggesting edits or adding content with a Pull Request diff --git a/_preview/419/_sources/foundations/github/git-branches.md b/_preview/419/_sources/foundations/github/git-branches.md deleted file mode 100644 index 5ee0bd1bc..000000000 --- a/_preview/419/_sources/foundations/github/git-branches.md +++ /dev/null @@ -1,304 +0,0 @@ -```{image} ../../images/Git-Logo-2Color.png -:alt: Git Logo -:width: 400px -``` - -# Git Branches - -Git "branches" are an important component of many Git and GitHub workflows. If you plan to use GitHub to manage your own resources, or contribute to a GitHub hosted project, it is essential to have a basic understanding of what branches are and how to use them. For example, the best practices for a simple workflow for suggesting changes to a GitHub repository are: create your own fork of the repository, make a branch from your fork where your changes are made, and then suggest these changes move to the upstream repository with a Pull Request. This section of the GitHub chapter assumes you have read the prior GitHub sections, are at least somewhat familiar with git commands and the vocabulary ("cloning," "forking," "merging," "Pull Request" etc), and that you have already created your own fork of the [GitHub Sandbox Repository](https://github.com/ProjectPythia/github-sandbox) hosted by Project Pythia. - -## Overview: - -1. What are Git Branches -1. Creating a New Branch -1. Switching Branches -1. Setting up a Remote Branch -1. Merging Branches -1. Deleting Branches -1. Updating Your Branches -1. Complete Workflow - -## Prerequisites - -| Concepts | Importance | Notes | -| ---------------------------------------------------------- | ----------- | ---------------------------- | -| [What is GitHub?](what-is-github) | Necessary | GitHub user account required | -| [GitHub Repositories](github-repos) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Cloning and Forking a Repository](github-cloning-forking) | Necessary | | -| [Configuring your GitHub Account](github-setup-advanced) | Recommended | | -| [Basic Version Control with _git_](basic-git) | Necessary | | - -- **Time to learn**: 30 minutes - ---- - -## What are Git branches? - -Git branches allow for non-linear or differing revision histories of a repository. At a point in time, you can split your repository into multiple development paths (branches) where you can make different commits in each, typically with the ultimate intention of merging these branches and development changes together at a later time. - -Branching is one of git's methods for helping with collaborative document editing, much like "change tracking" in Google Docs or Microsoft Word. It enables multiple people to edit copies of the same document content, while reducing or managing edit collisions, and with the ultimate aim of merging everyone's changes together later. It also allows the same person to edit multiple copies of the same document, but with different intentions. Some reasons for wanting to split your repository into multiple paths (i.e. branches) is to experiment with different methods of solving a problem (before deciding which method will ultimately be merged) and to work on different problems within the same codebase (without confusing which code changes are relevant to which problem). - -These branches can live on your computer (local) or on GitHub (remote). They are brought together through Git _pushes_, _pulls_, _merges_, and _Pull Requests_. _Pushing_ is how you transfer changes from your local repository to a remote repository. _Pulling_ is how you fetch upstream changes into your branch. _Merging_ is how you piece the forked history back together again (i.e. join two branches). And _Pull Requests_ are how you suggest the changes you've made on your branch to the upstream codebase. - -```{admonition} Pull Requests -:class: info -We will cover [Pull Requests]((github-pull-request)) more in-depthly in the next section. -``` - -One rule of thumb is for each development feature to have its own development branch until that feature is ready to be added to the upstream (remote) codebase. This allows you to compartmentalize your Pull Requests so that smaller working changes can be merged upstream independently of one another. For example, you might have a complete or near-complete feature on its own branch with an open Pull Request awaiting review. While you wait for feedback from the team before merging it, you can still work on a second feature on a second branch without affecting your first feature's Pull Request. **We encourage you to always do your work in a designated branch.** - -## Creating a New Branch - -```{admonition} Have you forked the repository? -:class: info -Having forked (NOT just cloned) the [GitHub Sandbox Repository](https://github.com/ProjectPythia/github-sandbox) is essential for following the steps in this book chapter. See the chapter on [GitHub Cloning and Forking](github-cloning-forking.md). -``` - -![branching](../../images/branching.gif) -The above flowchart demonstrates forking a remote repository, labeled "Upstream", creating a local copy, labeled "Clone", creating a new branch, "branchA", and adding two commits, C3 and C4, to "branchA" of the local clone of the forked repository. Different commits can be added to different branches in any order without depending on or knowing about each other. - -From your terminal, navigate to your local clone of your `Github-Sandbox` Repository fork: - -```bash -cd github-sandbox -``` - -Let's begin by checking the status of our repository: - -```bash -git status -``` - -![Git Status](../../images/1-gitstatus.png) - -You will see that you are already on a branch called "main". And that this branch is up-to-date with "origin/main" and has nothing to commit. - -```{admonition} The Main Branch -:class: info -Historically, the `main` branch was called the `master` branch. The name change was relatively recent, so all of your GitHub repositories may not reflect this yet. See instructions to change your branch name at [Github's Branch Renaming documentation](https://github.com/github/renaming). -``` - -Now check the status of your remote repository with - -```bash -git remote -v -``` - -![Git Remote](../../images/2-gitremote.png) - -We are set up to pull (denoted as 'fetch' in the output above) and push from the same remote repository. - -Next, check all of your exising Git branches with: - -```bash -git branch -a -``` - -![Git Branch](../../images/3-gitbranch.png) - -You will see one local branch (`main`) and your remote branch (`remotes/origin/HEAD` and `remotes/origin/main`, where `HEAD` points to `main`). `HEAD` is the pointer to the current branch reference, or in essence, a pointer to your last commit. More on this in a later section. - -Now, before we make some sample changes to our codebase, let's create a new branch where we'll make these changes: - -```bash -git branch branchA -``` - -Check that this branch was created with: - -```bash -git branch -``` - -![Git NewBranch](../../images/4-gitnewbranch.png) - -This will display the current and the new branch. You'll notice that current or active branch, indicated by the "\*" is still the `main` branch. Thus, any changes we make to the contents of our local repository will still be made on `main`. We will need to switch branches to work in the new branch, `branchA`. - -## Switching Branches - -To switch branches use the command `git checkout` as in: - -```bash -git checkout branchA -``` - -To check your current branch use `git status`: - -```bash -git status -``` - -![Git Checkout](../../images/5-gitcheckout.png) - -Notice that `git status` doesn't say anything about being up-to-date, as before. This is because this branch only exists locally, not in our upstream GitHub fork. - -## Setting up a Remote Branch - -While your clone lives locally on your laptop, a remote branch exists on your GitHub server. You have to tell GitHub about your local branch before these changes are reflected remotely in your upstream fork. - -![pushing](../../images/pushing.gif) -The above flowchart demonstrates pushing two new local commits (C3 and C4) to the corresponding remote branch. Before the push, the changes from these commits exist ONLY locally and are not represented on your upstream GitHub repository. After the push, everything is up-to-date. - -Before we push this branch upstream, let's make some sample changes (like C3 or C4) by creating a new empty file, with the ending ".py". - -```bash -touch hello.py -``` - -![Git Status](../../images/6-samplechange.png) - -You can check that this file has been created by comparing an `ls` before and after this command, and also with a `git status` that will show your new untracked file. - -`git add` and `git commit` your new file and check the status again. - -![Git Add](../../images/6a-gitadd.png) - -Your new branch is now one commit ahead of your main branch. You can see this with a `git log.` - -![Git Log](../../images/6b-gitlog.png) - -In a real workflow, you would continue making edits and git commits on a branch until you are ready to push up to GitHub. - -Try to do this with - -```bash -git push -``` - -![Git Push](../../images/6c-gitpush.png) - -You will get an error message, "fatal: The current branch `branchA` has no upstream branch." So what is the proper method for getting our local branch changes up to GitHub? - -First, we need to set an upstream branch to direct our local push to: - -```bash -git push --set-upstream origin branchA -``` - -Thankfully, Git provided this command in the previous error message. If you cloned using HTTPS, you will be asked to enter your username and password, as described in [GitHub's PAT Creation page](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). - -![Set Upstream](../../images/6d-setupstream.png) - -We can see that this worked by doing a `git branch -a` - -Notice the new branch called `remotes/origin/newbranch`. And when you do a `git status` you'll see that we are up to date with this new remote branch. - -![Git Commit Status](../../images/7-github-branchandstatus.png) - -On future commits you will not have to repeat these steps, as your remote branch will already be established. Simply push with `git push` to have your remote branch reflect your future local changes. - -## Merging Branches - -Merging is how you bring your split branches of a repository back together again. - -If you want to merge two _local_ branches together, the steps are as follows: - -Let's assume your two branches are named `branchA` and `branchB`, and you want your changes from `branchB` to now be reflected in `branchA` - -1. First checkout the branch you want to merge INTO: - -```bash -git checkout branchA -``` - -2. Then execute a `merge`: - -```bash -git merge branchB -``` - -If there were competing edits in the 2 branches that Git cannot automatically resolve, a **merge conflict** occurs. This typically happens if edits are to the same line in different commits. Conflicts can be [resolved in the command line](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line) or in your GUI of choice (such as Visual Studio Code). - -A **Pull Request** is essentially a merge that happens on an upstream remote. We will continue this demonstration and cover the specifics of merging via a [Pull Request](github-pull-request) more thoroughly in the next section. - -![PR](../../images/pullrequest.gif) -The above flowchart demonstrates a simple Pull Request where the upstream main repository has accepted the changes from the feature branch of your fork. The latest commit to the Upstream Main repository is now C4. Your Feature branch can now be safely deleted. - -## Deleting Branches - -After the feature you worked on has been completed and merged, you may want to delete your branch. -![deletebranch](../../images/deletingbranch.gif) - -To do this locally, you must first switch back to `main` or any non-target branch. Then you can enter - -```bash -git branch -d -``` - -for example - -```bash -git branch -d branchA -``` - -To delete the branch remotely, type - -```bash -git push --delete . -``` - -as in - -```bash -git push origin --delete jukent/branchA -``` - -## Updating Your Branches - -Previously, we showed you how to merge branches together, combining the changes from two different branches into one. Afterwards you deleted your feature branch `branchA`. Your local clone and fork of your `main` branch have now both need to pull from the upstream repository. - -![pull](../../images/pulling.gif) -The above flowchart demonstrates pulling in the upstream changes from Upstream Main after a Pull Request has been merged, first into your fork and then into your clone. Before continuing to work, with new commits on the feature branch, it is best to pull in the upstream changes. - -In this example, all of the changes to the branches were local and made by a single person, you. In a collaborative environment, other contributors may be making changes to their own feature branches (or main branch), which will ultimately be pushed up to the remote repository. Either way, your branches will become stale and need to be refreshed. The more time that passes by, the more likely this is to happen, particularly for an active GitHub repository. Here we show you how to sync your branches with the upstream branches. - -Once a Pull Request has been merged, you will find that these upstream changes are not automatically included in your fork or your other branches. In order to include the changes from the upstream main branch, you will need to do a `git pull`. - -First check if there are any upstream changes: - -```bash -git status -``` - -Then, if there are no merge conflicts: - -```bash -git pull -``` - -`git pull` is a combination of `git fetch` and `git merge`. That is it updates the remote tracking branches (`git fetch`) AND updates your current branch with any new commits on the remote tracking branch (`git merge`). - -This same concept appplies to work in a team setting. Multiple authors will have their own feature branches that merge into the same Upstream Main repository via Pull Requests. It is important for each author to do regular `git pulls` to stay up to date with each other's contributions. - -## Complete Workflow - -All in all your Git Branching workflow should resemble this flow: -![gitworkflow](../../images/gitworkflow.gif) - -1. Forking the upstream repository -1. Creating a local clone of your upstream fork -1. Creating a new branch -1. Switching branches -1. Making a commit -1. Setting up a remote branch -1. Merging branches via a PR -1. Deleting branches -1. Pulling from upstream - ---- - -## Summary - -- Git Branches allow you to independently work on different features of a project via differing revision histories of a repository. -- A useful workflow is to create a new branch locally, switch to it and set up a remote branch. During your revision, push to your upstream branch and pull from main as often as necessary. Then suggest your edits via a Pull Request and, if desired, delete your branch after the merge. - -### What's Next? - -[Opening a Pull Request on GitHub](github-pull-request) - -## Resources and references - -- [GitHub.com Help Documentation (GitHub Docs)](https://docs.github.com/en) -- [Xdev Python Tutorial Seminar Series - Github (Kevin Paul)](https://www.youtube.com/watch?v=fYkPn0Nttlg) -- [Resolving a Merge Conflict Using the Command Line (GitHub Docs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line) diff --git a/_preview/419/_sources/foundations/github/github-advanced.md b/_preview/419/_sources/foundations/github/github-advanced.md deleted file mode 100644 index 0e00ec3eb..000000000 --- a/_preview/419/_sources/foundations/github/github-advanced.md +++ /dev/null @@ -1,38 +0,0 @@ -# Advanced GitHub Topics - -```{note} -This content is under construction! -``` - -## Overview: - -1. Overview 1 -1. Overview 2 - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------- | ---------- | ----- | -| Prior GitHub Sections | Necessary | | - -- **Time to learn**: 30 minutes - ---- - -## Content section - ---- - -## Summary - -- Sum 1 -- Sum 2 - -### What's Next? - -End of GitHub content - -## References - -1. Ref 1 -1. Ref 2 diff --git a/_preview/419/_sources/foundations/github/github-cloning-forking.md b/_preview/419/_sources/foundations/github/github-cloning-forking.md deleted file mode 100644 index bdb5205f1..000000000 --- a/_preview/419/_sources/foundations/github/github-cloning-forking.md +++ /dev/null @@ -1,236 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# Cloning and Forking a Repository - -## Overview: - -1. Cloning and forking a git repository -1. Cloning a repository -1. Forking a repository - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------- | ----------- | ---------------------------- | -| [What is GitHub?](what-is-github) | Necessary | GitHub user account required | -| [GitHub Repositories](github-repos) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| Command-line shell | Helpful | | - -- **Time to learn**: 30 minutes - ---- - -## Cloning and forking - -_Cloning_ and _forking_ are two related terms in the GitHub vernacular -that, unfortunately, are not always used consistently throughout -the _web-o-sphere_. In Project Pythia we use the term _clone_ to refer to -making a **local** copy of a **remote** repository; the source for -the copy is a remote repo, and the destination for the copy is your -local laptop/desktop. When working with GitHub, a _fork_, on the -other hand, creates a copy of a GitHub repository on GitHub. In other -words, both the source and the destination of the _fork_ operations are -hosted in the cloud on GitHub. Forking is performed via your GitHub -account. While the forked repository may be owned by anyone, the -newly created repository will be owned by you. Cloning, on the -other hand, is performed using a Git command. Naturally, since the -destination of the clone operation is your local computer, you will -own the cloned contents. In either case, whether you clone or fork, -any changes you make to the newly created repository will not impact -the original without taking explicit action (e.g. performing a -_push_ or submitting a _Pull Request_, the topics of later sections -in this guide). - -Cloning and forking are often used together (more on this later). -The illustration below demonstrates the operation of a Fork of a -remote repository (UPSTREAM), followed by a clone of the newly -created ORIGIN. - -![clone-and-fork](../../images/github-clone-fork.png) - -## Cloning a repository - -Cloning is ideal for the following scenarios: - -1. You wish to download, build, and install the latest version of a software package. -1. You would like to experiment with a repository on your local computer, but do not desire to maintain a separate copy of it (termed a _fork_, to be covered later in this lesson) on your GitHub account. -1. You have previously _forked_ a repository to your own GitHub account, and now wish to make changes to it for possible incorporation into the original repo, via a _Pull Request_. - -Let's consider the 2nd scenario. Say you wish to copy a GitHub repository to a computer you have access to (which could be your own computer, or one you have access to at work or school). - -We'll use a very basic repo that is part of the [Project Pythia organization](https://github.com/ProjectPythia) as our example. - -First, point your browser to : - -SandboxRepo - ---- - -We see that in the repository, there exists five files. Above the list of files is this row: - -RepoTools - ---- - -Click on the green **Code** button to the right: - -CodeClone - ---- - -Select the **HTTPS** option, and click on the copy-to-clipboard icon: - -CodeCloneHTTPS - ---- - -```{tip} -This link points to where the repository "lives" on GitHub. We will use the term **origin** to refer to this location. -``` - -Now, open up a terminal on your local computer, and if desired, `cd` into a directory that you'd like to house whatever repos you clone. Type `git clone`, and then paste in the URL that you copied from GitHub (i.e., the **origin**): - -``` -git clone https://github.com/ProjectPythia/github-sandbox.git -``` - -You'll see something like the following: - -``` -Cloning into 'github-sandbox'... -remote: Enumerating objects: 15, done. -remote: Counting objects: 100% (15/15), done. -remote: Compressing objects: 100% (14/14), done. -remote: Total 15 (delta 3), reused 0 (delta 0), pack-reused 0 -Receiving objects: 100% (15/15), 7.41 KiB | 2.47 MiB/s, done. -Resolving deltas: 100% (3/3), done. -``` - -```{admonition} Windows users -:class: info -While `git` is typically part of a Linux or Mac OS command-line shell, similar functionality must be installed if you are running Windows. Download and install the [Git for Windows](https://gitforwindows.org/) package. -``` - -Now, you can `cd` into the `github-sandbox` directory which has been created and populated with the exact contents of the **origin**'s repository at the time you cloned it. If you have a Python installation, you could then type - -``` -python sample.py -``` - -to run the sample Python script. You should see the following output: - -``` -Hello, Python learners! -``` - -By virtue of cloning the repo, _git_ automatically registers the URL of the **origin**'s repository on GitHub. You can show this by typing the following: - -``` -git remote -v -``` - -You should see: - -``` -origin git@github.com:ProjectPythia/github-sandbox.git (fetch) -origin git@github.com:ProjectPythia/github-sandbox.git (push) -``` - -```{tip} -We discuss the `git` command-line interface in the [Basic version control with git](basic-git) lesson. -``` - -**Congratulations!** You have now cloned a GitHub repository! - -Now, let's consider the 3rd scenario for cloning... which involves the related topic of _forking_. - -## Forking a repository - -Forking is similar to cloning, but has a bit more involved workflow. Scenarios where forking a repo is indicated include the following: - -1. You wish to collaborate on projects that are hosted on GitHub, but you are not one of that project's _maintainers_ (i.e., you do not have _write permissions_ on it). -1. You wish to experiment with changing or adding new features to a project, and do not immediately intend to _merge_ them into the original project's repo (aka, the _upstream_ repository). - -In a fork, you create a copy of an existing repository, but store it in your own personal GitHub organization (recall that when you create a GitHub account, the _organization_ name is your GitHub user ID). - -Let's say we intend to make some changes to the [Project Pythia Sandbox](https://github.com/ProjectPythia/github-sandbox) repo, that ultimately we'll submit to the original repository as a _Pull request_. - -```{note} -Be sure you have logged into GitHub at this time! -``` - -Notice at the top right of the screen, there is a _Fork_ button: - -Fork - ---- - -Click on it: - -ForkDest - ---- - -You should see your GitHub user ID (if you administer any other GitHub organizations, you will see them as well). Click on your user ID to complete the _fork_. After a few seconds, your browser will be redirected to the forked repo, now residing in your personal GitHub organization: - -ForkPost - ---- - -Notice that the _Fork_ button on the upper right has incremented by one, and there is also is a line relating your fork to the original repo: - -ForkBranch - ---- - -```{tip} -We discuss *branches* in the [Git Branches](git-branches) lesson. -``` - -You now have a copy (essentially a clone) of the forked repository, which is now owned by you. - -You could, at this point, select one of the files in the repository and use GitHub's built-in editor to make changes to these text-based files. However, the typical use case that leverages the collaborative power of GitHub and its command-line cousin, _git_, involves _cloning_ your _forked_ copy of the repo to your local computer, where you can then perform your edits, and (in the case of software) test them on your system. - -Cloning your fork is the same as cloning the original repo. Click on the Code button, select the HTTPS protocol, copy the URL to the clipboard, and then run `git clone ` on your local computer. In this case, you will need to either run this command in a different directory, or rename the destination directory with `git clone `, since it will by default use the name of the repo, `github-sandbox`. - -```{tip} -Unlike cloning, forking is not an option supported by the *git* command-line interface. In other words, `git fork` is not a valid command. -``` - -Once you've cloned the fork to your local machine, try running `git remote -v` again. You will see that the _origin_ URL now points to your GitHub account or organization. - -_The main purpose of cloning and forking a remote repository is so that you can make changes to the contents of those repositories in a safe and version-controlled manner._ The process of making changes and submitting them as _Pull Requests_ to the original repository is covered in our lesson on [Opening a Pull Request on GitHub](github-pull-request), but the workflow is as follows: - -1. Edit an existing file or files, and/or create new files. -1. Stage your changes by running `git add`. -1. Commit your changes by running `git commit`. -1. (If you created a fork): Push your changes to your _fork_ by running `git push`. -1. (If you did not create a fork): Push your changes to the _upstream_ repository by running `git push`. This assumes you have write permissions on the _upstream_ repository. -1. In GitHub, create a _Pull request_. - ---- - -## Summary - -- The process of making a **local** copy of a GitHub repository is called _cloning_. The destination for the cloned copy is whatever machine you ran the `git clone` command from. -- _Forking_ a repository also makes a copy of a GitHub repo, but places it in your GitHub organization in the GitHub.com cloud. -- Forking allows you to modify a remote repo, without affecting the original version. -- After cloning your fork to your local computer, you can make changes to your copy, which you can then submit to the original repo as a [_Pull request_](github-pull-request). - -## Things to try - -- Clone another GitHub-hosted repository that is of interest to you. -- Try creating a fork of that repository. - -### What's Next? - -In the next lesson, you will set some configurations on your GitHub account that enable uploads (aka _pushes_) from your local computer to GitHub. You will also configure notifications on your GitHub account. - -## References - -1. [Cloning vs Forking (GitHub Support)](https://github.community/t/the-difference-between-forking-and-cloning-a-repository/10189) -1. [What the Fork?(GitHub Community)](https://github.community/t/what-the-fork/10187) diff --git a/_preview/419/_sources/foundations/github/github-issues.md b/_preview/419/_sources/foundations/github/github-issues.md deleted file mode 100644 index d11843cf7..000000000 --- a/_preview/419/_sources/foundations/github/github-issues.md +++ /dev/null @@ -1,100 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# Issues and Discussions - -## Overview: - -1. What are Issues and Discussions? -1. Examine an existing Issue -1. Examine an existing Discussion - -## Prerequisites - -| Concepts | Importance | Notes | -| ----------------------------------- | ---------- | ----- | -| [What is GitHub?](what-is-github) | Necessary | | -| [GitHub Repositories](github-repos) | Necessary | | - -- **Time to learn**: 5 minutes - ---- - -## What are Issues and Discussions? - -GitHub provides two different, but related mechanisms for communicating -within a repository about a project: _Issues_ and _Discussions_. -Issues are more like “todo” items; they are task-focused. For example, Issues -are often used to report and track bugs, request new features, or -perhaps note a performance problem. Ultimately, the maintainers of -a project may resolve the issue by fixing the bug, adding the -feature, etc., and then closing the resolved issue, marking the -task as completed. GitHub _Discussions_, much like the name implies, -are more open ended, and may not have a resolution. Asking about a -topic, discussing the merits of a new feature, or even advertising -an event, such as a tutorial for your project, are all examples -of _Discussions_. - -In the text below we discuss _Issues_ in more detail, followed by -a discussion on Discussions. Keep in mind that when initiating a -conversation on GitHub, it is often unclear whether something is -more suited as an _Issue_ or a _Discussion_. We, the creators of -Project Pythia, struggle with this ourselves. If you’re not sure, simply pick -one. Fortunately, the GitHub developers recognized this dilemma, and -made it easy to convert _Issues_ into _Discussions_ and vice versa. - -## Issues - -To get started, let's take a look at the [Issues page](https://github.com/ProjectPythia/pythia-foundations/issues) in Project Pythia's `pythia-foundations` repository: - -Pythia Issues - -By default, it shows all open Issues, but we can see all [closed Issues](https://github.com/ProjectPythia/pythia-foundations/issues?q=is%3Aissue+is%3Aclosed) by clicking "Closed". - -Pythia Closed Issues - -Issues, Discussions, and Pull Requests are all numbered for easy reference. By opening, resolving, and then closing an issue, we are leaving behind a searchable public record of what the issue was, why we thought it was important, and how we resolved it. This is great for project management, since it gets old Issues out of the way without actually deleting them. - -Let's now examine [Issue \#144](https://github.com/ProjectPythia/pythia-foundations/issues/144). - -Pythia Issue 144 - -As you can see, some broken links were found in one of the Pythia Foundations tutorials, likely because the site being linked recently had its structure changed. An additional comment was added, as well as a label to help filtering/sorting Issues by topic. We then see that this issue was mentioned (by typing the issue number) elsewhere in the repository. In this case, it was mentioned in [Pull Request \#145](https://github.com/ProjectPythia/pythia-foundations/pull/145), which makes the changes to fix the issue. We can also see that the PR has been merged, which means the changes have been incorporated into the main branch of the code. - -Like this example, Issues can notify others of bugs or typos, but they can also be used as "calls to action", whether you plan on addressing the issue yourself, or are hoping that someone else will be interested in making the changes. Issues [\#97](https://github.com/ProjectPythia/pythia-foundations/issues/97) and [\#98](https://github.com/ProjectPythia/pythia-foundations/issues/98) are examples of this, in which ideas for changes are proposed and then addressed at a later time. - -A new issue can be opened by pressing the "New issue" button on the top right of the Issues page. Depending on the repository, you may be prompted to choose from a template, or you may just see title and text boxes to fill out. - -## Discussions - -Discussions, on the other hand, are more open-ended and do not _necessarily_ suggest a change or addition to the repository. Here is the [Discussions page for Pythia Foundations](https://github.com/ProjectPythia/pythia-foundations/discussions): - -Pythia Foundations Discussions - -Let's take a look at [Discussion \#156](https://github.com/ProjectPythia/pythia-foundations/discussions/156). - -Pythia Discussion 156 - -This discussion brings up a resource relevant to the repository that could help others, but it is not suggesting a change like an issue would. Other Discussions might include announcements, Q&A, or general thoughts about the repository. - -GitHub also makes it simple to reference a Discussion in an Issue (and vice versa), -which can help provide background and context for a piece of work. - ---- - -## Summary - -- GitHub provides Issues and Discussions to facilitate collaboration. -- Issues are specific and actionable, while Discussions are open-ended. -- If you want to discuss a topic and you're not sure if it is an Issue - or a Discussion, just pick one. It will be okay. :-) - -### What's Next? - -We will work through cloning and forking an example repository. - -## References - -1. [What is GitHub Discussions? A complete guide](https://resources.github.com/devops/process/planning/discussions/) diff --git a/_preview/419/_sources/foundations/github/github-pull-request.md b/_preview/419/_sources/foundations/github/github-pull-request.md deleted file mode 100644 index b1b720104..000000000 --- a/_preview/419/_sources/foundations/github/github-pull-request.md +++ /dev/null @@ -1,169 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# Opening a Pull Request on GitHub - -A Pull Request, aka a "merge request," is an event that occurs when a project contributor begins the process of merging new code changes from a feature branch with the main project repository. - -## Overview: - -1. What is a Pull Request? -1. Opening a Pull Request -1. Pull Request Features -1. GitHub Workflows - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------- | ----------- | ----- | -| [What is GitHub](what-is-github) | Necessary | | -| [GitHub Repositories](github-repos) | Necessary | | -| [Cloning and Forking](github-cloning-forking) | Necessary | | -| [Basic Version Control with _git_](basic-git) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Branches](git-branches) | Necessary | | - -- **Time to learn**: 60 minutes - ---- - -## What is a Pull Request? - -A Pull Request (PR) is a formal mechanism for requesting that changes -that you have made to one repository are integrated (merged) into -another repository. Typically, the changes are reviewed by the -maintainers of the destination repository, potentially triggering -a cycle of revisions, before the PR is “merged”, and your changes -become part of the destination repo. - -Just like Issues, PRs have -their own discussion forum for communicating about the proposed -changes. In fact, not only can maintainers or collaborators communicate -about your PR via GitHub, they can also suggest changes and may -even be able to make changes of their own by pushing follow-up -commits. All of the activity, from start to finish, is tracked -inside of the PR and can be reviewed at any time. - -When a contributor to a project creates a PR they are requesting -that the owners of another destination repository pull a git -branch from the contributor’s repository and merge the contents of -the branch into a branch of the destination repository. This means -that the contributor must provide four pieces of information: the -contributor’s repository, the contributor’s branch, the destination -repository, and finally, the destination branch. - -A typical sequence of steps consists of the following: - -1. A contributor clones a personal remote repository, creating a local copy -1. The contributor creates a new branch in their local repository -1. The contributor makes changes to the branch and commits them to - their local repository -1. The contributor _pushes_ the branch to a remote repository -1. The contributor submits a PR via GitHub - -After the maintainers or collaborators of the destination review -the changes, and any suggested revisions are made, the project -maintainer merges the feature into the destination repository and -closes the PR. - -## Opening a Pull Request - -The demonstration is a continuation from the [GitHub Branches chapter](github-branches). Here, we will move from your local terminal to GitHub. - -### Navigate to Your Fork - -Go to your fork of the [GitHub Sandbox Repository](https://github.com/ProjectPythia/github-sandbox). One fast way to get to your fork, is to click the "fork" button and then follow the link underneath the message, "You've already forked github-sandbox." - -When you've navigated to your fork, you should see a message box alerting you that your branch `branchA` had recent changes with the option to generate an open Pull Request. This Pull Request would take the changes from your `branchA` branch and suggest them for the original upstream ProjectPythia github-sandbox repository. You'll also notice that you are on branch `main`, but that there are now 2 branches. - -![GitHub](../../images/8-github.png) - -### Switch Branches - -If you click on the branch `main` you'll see the list of these branches. - -![GitHub Branches](../../images/9-github-seebranches.png) - -There you can click on the branch `branchA` to switch branches. - -![New Branch](../../images/10-github-newbranch.png) - -Here you will see the message, "This branch is 1 commit ahead of ProjectPythia:main." Next to this message you'll see either the option to "Contribute" (which opens a Pull Request) or "Fetch Upstream" (which pulls in changes from the original repository). And just above your files you'll see your most recent commit. - -### Open a Draft Pull Request - -Click on the "Open pull request" button under the "Contribute" drop-down. - -![Contribute](../../images/11-newbranch-contribute.png) - -This will send you to a new page. Notice that you are now in "ProjectPythia/github-sandbox" and not your fork. - -![Compare](../../images/12-compare.png) - -The page will have the two branches you are comparing with an arrow indicating which branch is to be merged into which. Here, `base` is the upstream origin and `head` is your forked repository. If you wanted, you could click on these branches to switch the merge configuration. Underneath that you'll see a green message, "Able to merge. These branches can be automatically merged." This message means that there are no conflicts. We will discuss conflicts in a later chapter. - -In a one-commit PR, the PR title defaults to your commit message. You can change this if you'd like. There is also a space to add a commit message. This is your opportunity to explain your changes to the owners of the upstream repository. - -![Message](../../images/13-message.png) - -And if you scroll down, you'll see a summary of this PR with every commit and changed file listed. - -![Summary](../../images/14-prsummary.png) - -Click the arrow next to "Create Pull Request" to change this to a draft PR. - -![To Draft](../../images/15-todraft.png) - -Once you've clicked "Draft Pull Request," you will be directed to the page of your new PR. Here you can add more comments or request reviews. - -![Draft PR](../../images/16-draft.png) - -## Pull Request Features - -Now let's look at the features and discussions in an open (draft) PR. -Clicking "Files Changed" allows you to see all of the changes that would be merged with this PR. - -![Files](../../images/17-fileschanged.png) - -If you are working in a repository that has automatic checks, it is a good idea to wait for these checks to pass successfully before you request reviewers or change to a non-draft PR. Do this by clicking "Ready for Review." - -![Review](../../images/18-review.png) - -When working on a project with a larger team, do NOT merge your Pull Request before you have the approval of your teammates. Every team has their own requirements and best practice workflows, and will discuss/approve/reject Pull Requests together. We will cover more about the ways to interact with PRs through conversations and reviews in a later section. - -To someone with write permissions on the repository, the ability to merge will look like this green button: -![Green](../../images/20-green.png) - -However, this PR will NOT be merged, as the GitHub-Sandbox repository is intended to be static. - -## GitHub Workflows - -The above demonstration is an example of the Git **Forking Workflow**, because we forked the [GitHub Sandbox repository](https://github.com/ProjectPythia/github-sandbox) before making our feature branches. This is most common when you do NOT have write-access to the upstream repository. - -This differs from the **Feature Workflow**, where all contributors work on a single, remote GitHub repository in specific feature branches. This is common when all contributors DO have write-access to the upstream repository. - -The steps leading up to creating your PR depend on your workflow. The main difference in creating the PR is that -the contributor now, for the Feature Workflow, navigates to the upstream, remote -repository, not a personal remote fork, and initiates the PR there. - -We will cover [GitHub Workflows](github-workflows) in greater detail in the next chapter. - ---- - -## Summary - -- A Pull Request (PR) is a formal mechanism for requesting that changes - that you have made to one repository are integrated (merged) into - another repository. -- The steps that lead up to - the PR depend your GitHub Workflow. - -### What's Next? - -In the next lesson we will learn more about [Reviewing Pull Requests](review-pr). - -## References - -1. GitHub's [Collaborating with Pull Requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) diff --git a/_preview/419/_sources/foundations/github/github-repos.md b/_preview/419/_sources/foundations/github/github-repos.md deleted file mode 100644 index 4782dddeb..000000000 --- a/_preview/419/_sources/foundations/github/github-repos.md +++ /dev/null @@ -1,104 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# GitHub Repositories - -## Overview: - -1. Explore GitHub Repositories - -## Prerequisites - -| Concepts | Importance | Notes | -| ----------------------------------------------------------------------------------------------- | ---------- | ----- | -| [What is GitHub?](https://foundations.projectpythia.org/foundations/github/what-is-github.html) | Necessary | | - -- **Time to learn**: 15 minutes - ---- - -## What is a GitHub repository? - -GitHub gives the following explanation of a [repository](https://docs.github.com/en/get-started/quickstart/hello-world): - -> A repository is usually used to organize a single project. Repositories can contain folders and files, images, videos, spreadsheets, and data sets -- anything your project needs. Often, repositories include a `README` file, a file with information about your project. GitHub makes it easy to add one at the same time you create your new repository. It also offers other common options such as a license file. - -In short, it is a _collection of files_. Each GitHub repository has an _owner_, which could be an individual or an organization. Repositories can also be set to _public_ or _private_, determining who can see and interact with it. While a repository can simply store files, GitHub is designed with **collaboration** in mind. Three key collaborative tools in GitHub are: - -1. **Issues**: report a bug, plan improvements, or provide feedback to others working on the repository. -1. **Discussions**: post ideas or other conversations that are not as specific or actionable as an **Issue**. -1. **Pull requests**: We will go into the specifics later, but a **Pull request** allows a user to _propose a change_ to any of the files within a repository. - -```{admonition} Tip -:class: tip -Typically, a GitHub repository will always include the **Issues** and **Pull requests** tabs. **Discussions** are not enabled by default, but are increasingly prevalent. -``` - -## What are some examples of repositories? - -All of the Python packages covered (e.g. [Numpy](https://github.com/numpy/numpy) and [Xarray](https://github.com/pydata/xarray)) in this Foundations book have associated GitHub repositories, as well as [Python itself](https://github.com/python/cpython): - -NumPy GitHub - -Xarray GitHub - -Python GitHub - -As you can see by the recent timestamps, these repositories are actively changing; this reflects the adaptability of the [open-source software](https://opensource.org/osd) ecosystem surrounding Python. - -```{admonition} Tip -:class: tip -Notice that each of the three *Repositories* each exist as part of their own *Organization*. In other words, the NumPy repository exists within the NumPy organization; the Xarray repo exists within the Pydata org, and so forth. - -When you [create your own GitHub account](https://foundations.projectpythia.org/foundations/github/what-is-github.html), your user ID functions as the *organization*. Any repositories you create (and therefore, *own*) will exist within that org. -``` - -Another example is this project's [Pythia Foundations repository](https://github.com/ProjectPythia/pythia-foundations), on which this tutorial is stored. It is owned by the [Project Pythia organization](https://github.com/ProjectPythia). This organization also owns several other repositories that store the files needed to generate , among other things. - -## GitHub's distributed repositories - -Finally, we introduce an important concept that is vital to your -understanding when working with GitHub. It is the source of GitHub's power, as well -as much of its complexity. GitHub repositories -are _distributed_; in the general case, there is more than one -repository for any project. In fact, repositories can come and go -at any time, created and deleted as need dictates. Creating new -repositories from existing ones, synchronizing them, and managing them -are the topics of later sections. For now, it is only important to -understand that for a GitHub-managed project, there is typically one -"official" repository, often called the "upstream" repository, and it lives on GitHub.com. There may be any -number of copies of the "official" repository, known as _forks_ (or _origins_, -if it is owned by you), -that also reside on GitHub.com. Repos that are hosted on GitHub.com -are referred to as _remotes_. In addition to the remotes, there may -be one or more copies of the remotes on your desktop or laptop -computer that are referred to as _locals_. A conceptual diagram of -the various repos is shown in the image below. - -![GitHub repositories](../../images/github-repos.png) - ---- - -## Things to try: - -1. Browse the [NumPy](https://github.com/numpy/numpy), [Xarray](https://github.com/pydata/xarray), [Python](https://github.com/python/cpython), and [Pythia Foundations](https://github.com/ProjectPythia/pythia-foundations) repos. -1. Browse the organizations (e.g., [Pydata](https://github.com/pydata)) which house the repos within. -1. Check out GitHub's ["Create a repo"](https://docs.github.com/en/get-started/quickstart/create-a-repo) tutorial to learn how to create your own repository! - ---- - -## Summary - -- GitHub's Repositories are collections of files. -- Issues, Discussions, and Pull requests can be used to collaborate within a repository. -- A GitHub _Organization_ contains _Repositories_. - -### What's Next? - -We will further explore Issues and Discussions. - -## References - -1. [GitHub's quickstart guide](https://docs.github.com/en/get-started/quickstart) diff --git a/_preview/419/_sources/foundations/github/github-setup-advanced.md b/_preview/419/_sources/foundations/github/github-setup-advanced.md deleted file mode 100644 index e66244637..000000000 --- a/_preview/419/_sources/foundations/github/github-setup-advanced.md +++ /dev/null @@ -1,193 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# Configuring Your GitHub Account - -## Overview: - -1. Configure your GitHub account for secure logins via ssh and/or https -1. Set up notifications on repositories you own or follow - -## Prerequisites - -| Concepts | Importance | Notes | -| ---------------------------------------------------------- | ----------- | ---------------------------- | -| [What is GitHub?](what-is-github) | Necessary | GitHub user account required | -| [GitHub Repositories](github-repos) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Cloning and Forking a Repository](github-cloning-forking) | Recommended | | - -- **Time to learn**: 35 minutes - ---- - -## GitHub secure key generation - -When you signed up for your free account on [GitHub](https://github.com), you established a _user ID_ and its corresponding _password_. Many of the repositories that GitHub serves are readable from anywhere, not even requiring a GitHub account. - -However, especially when you use the git command-line interface to access a GitHub-hosted repo, there are cases when you need to provide an additional set of login credentials. Some of these cases are: - -1. When you want to clone a _private_, as opposed to _public_ GitHub repository (**read-access**) -2. When you wish to _push_ to a repo (**write-access**) - -For these use-cases, you won't be able to simply type your GitHub user ID and password from the command line. Instead, you need to set up _access tokens_ that live in two places: in your GitHub account, and in your local computer's file system. - -GitHub supports two means of key-based access: via _https_, and via _ssh_. - -For example, one can clone [Project Pythia's Sandbox repository](https://github.com/ProjectPythia/github-sandbox) using a URL for the https protocol: - -```{image} ../../images/GitHub_Setup_Advanced_https_URL.png -:alt: GitHub Clone https -:width: 600px -``` - ---- - -The URL in this case is **https://github.com/ProjectPythia/github-sandbox.git** - -Similarly, if you click on the **SSH** tab: - -```{image} ../../images/GitHub_Setup_Advanced_ssh_URL.png -:alt: GitHub Clone ssh -:width: 600px -``` - ---- - -Here, the URL is **git@github.com:ProjectPythia/github-sandbox.git** - -## Generate a secure personal access token for https - -First, you will create a secure token in your GitHub account settings, and then use the token on your local computer. - -Follow the steps with helpful screenshots at [GitHub's PAT Creation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) page. - -```{admonition} Tip: -:class: tip -If using the *https* protocol to *push* to a remote repo, you must have generated and downloaded a personal access token. You *may* also need it when cloning, *if* the remote repo is *not* open to all. -``` - -## Generate an SSH public/private keypair - -First, on your local computer, you will create an SSH _public/private keypair_, and then upload the _public key_ to your GitHub account. - -Follow the steps with helpful screenshots at [GitHub's Connecting to GitHub with SSH](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) page. - -```{admonition} Tip: -:class: tip -If using the *ssh* protocol to clone *or* push, you *must* have generated and created an ssh key-pair. -``` - ---- - -```{admonition} HTTPS vs SSH: Either is fine! -:class: note -Either **https** or **ssh** works fine. Choose whatever you prefer. See [this overview](https://www.toolsqa.com/git/ssh-protocol/) of the pros and cons of each protocol. -``` - ---- - -## GitHub notifications - -In keeping with the social network aspect of GitHub, you can _follow_ particular repositories that are of interest to you. Additionally, once you begin contributing to a repository, you may wish to be notified when Pull Requests are made, Issues are posted, your code review is requested, and so on. While it's easy to have GitHub email you at the address you used when you registered for your GitHub account, you may wish to avoid email clutter. - -### Email notifications - -Let's say you wish to monitor (or _watch_) the Project Pythia GitHub Sandbox repository and receive emails about it. - -Click on the **Watch** link near the top of the page: - -```{image} ../../images/GitHub_Setup_Advanced_Watch.png -:alt: GitHub Watch -:width: 600px -``` - ---- - -You can then select what type of notifications you wish to receive. For example, you may want to receive _all notifications_ related to that repo: - -```{image} ../../images/GitHub_Setup_Advanced_Watch_All_Activity.png -:alt: GitHub Watch All Activity -:width: 600px -``` - ---- - -You will then receive email at the address you used when you signed up for GitHub whenever activity occurs on that repo. - -```{image} ../../images/GitHub_Setup_Advanced_Unwatch.png -:alt: GitHub Unwatch -:width: 600px -``` - ---- - -You can stop watching that repo by just clicking on the now-labeled _Unwatch_ link again, and choosing _Participating and @mentions_ to toggle it back to _Unwatch_. - -## Stop spamming me, GitHub! - -It's easy to become overwhelmed with email from one or more repos that you are following and/or participating in! In this case, you may wish to disable email notifications. -In order to set your notification settings, go to **https://github.com/settings/notifications**. You can, for example, uncheck the **Email** boxes to cease receiving notifications that way: - -```{image} ../../images/GitHub_Setup_Advanced_Notification_Settings.png -:alt: GitHub Notification Settings -:width: 600px -``` - ---- - -If you turn email notifications off, get in the habit of clicking on the _Notifications_ icon when logged into GitHub: - -```{image} ../../images/GitHub_Setup_Advanced_Notifications.png -:alt: GitHub Notifications -:width: 600px -``` - ---- - -You can click on the _Notifications_ icon and scroll through all notifications from repos that you opted into receiving notifications from: - -```{image} ../../images/GitHub_Setup_Advanced_Notifications_Browser.png -:alt: GitHub Notification Browser -:width: 600px -``` - ---- - -Use the _Filter notifications_ control to display only those that meet certain criteria. For example, say you only wanted to view topics related to the _MetPy_ repo: - -```{image} ../../images/GitHub_Setup_Advanced_Notification_Filter.png -:alt: GitHub Notification Filter -:width: 600px -``` - ---- - -```{admonition} Tip: -:class: tip -In the list of notifications, you can unsubscribe as shown below. -``` - -```{image} ../../images/GitHub_Setup_Advanced_Notifications_Unsubscribe.png -:alt: GitHub Notification Unsubscribe -:width: 600px -``` - ---- - -## Summary - -- GitHub uses **secure tokens** to enable _write_ (and sometimes _read_) _access_ to GitHub repositories. -- You can opt-in to notifications on a repo. The default, which can be easily changed, is to receive email. - -### What's Next? - -In the next section, we will learn the basics of version control using command-line `git`. - -## References - -1. [GitHub Personal Access Token (https)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) -1. [GitHub Public/Private Keypair (ssh)](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) -1. [Remotes in GitHub (Carpentries Tutorial)](https://swcarpentry.github.io/git-novice/07-github.html) diff --git a/_preview/419/_sources/foundations/github/github-workflows.md b/_preview/419/_sources/foundations/github/github-workflows.md deleted file mode 100644 index eb83969f1..000000000 --- a/_preview/419/_sources/foundations/github/github-workflows.md +++ /dev/null @@ -1,469 +0,0 @@ -```{image} ../../images/Git-Logo-2Color.png -:alt: Git Logo -:width: 400px -``` - -# GitHub Workflows - -A workflow is a series of activities or tasks that must be completed sequentially or parallel to achieve the desired outcome. Here we outline two different GitHub workflows that take you through the steps leading up to opening a Pull Request. - -## Overview: - -1. GitHub workflows overview -1. Git Feature Branch Workflow -1. Forking workflow - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------- | ----------- | ----- | -| [What is GitHub](what-is-github) | Necessary | | -| [GitHub Repositories](github-repos) | Necessary | | -| [Cloning and Forking](github-cloning-forking) | Necessary | | -| [Basic Version Control with _git_](basic-git) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Branches](git-branches) | Necessary | | -| [Pull Requests](github-pull-request) | Necessary | | -| [Reviewing Pull Requests](review-pr) | Recommended | | - -- **Time to learn**: 60 minutes - ---- - -## GitHub workflows - -GitHub, together with Git, are powerful tools for managing and -collaborating on all kinds of digital assets, such as software, -documentation, and even manuscripts for research papers. Like other -complex software environments, often these tools can be employed -in many different ways to accomplish the same goal. In order to -effectively and consistently use Git and GitHub, over the years a -variety of best practices have evolved for supporting different -modes of collaboration. Collectively these different models, or -recipes, are referred to as _workflows_. - -A typical sequence of workflow steps consists of the following: - -1. A contributor clones a personal remote repository, creating a local copy -1. The contributor creates a new branch in their local repository -1. The contributor makes changes to the branch and commits them to - their local repository -1. The contributor _pushes_ the branch to a remote repository -1. The contributor submits a PR via GitHub - -The sequence of steps -outlined above provides a general framework for submitting a PR. -But the precise set of steps is highly dependent on the choice of -workflow for a given project. In this chapter we describe Pull -Requests for two commonly used workflows: The **Git Feature Branch -Workflow** and the **Forking Workflow**. The former is simpler and often -used by teams when everyone on the team is an authorized contributor -to the destination repository. I.e. all of the contributors have -write access to the remote repository hosted by GitHub. The latter -is typically what is needed to contribute to external projects for -which the contributor is not authorized (i.e. does not have write -access) to make changes to the destination repository. We briefly -describe both workflows below, and include the steps necessary to -make a PR on each. - -## Git Feature Branch Workflow - -The **Git Feature Branch Workflow** is one of the simplest and oldest -collaborative workflows that is used for small team projects. The -key idea behind this workflow, which is also common to the **Forking -Workflow**, is that all development (all changes) should take place -on a dedicated Git _feature_ branch, not the _main_ (historically -referred to as _master_) branch. The motivation behind this is that -one or more developers can iterate over a feature branch without -disturbing the contents of the main branch. Consider using the **Git -Feature Branch Workflow** for GitHub’s most widely used purpose, -software development. Software modifications are liable to introduce -bugs. Isolating them to a dedicated branch until they can be fixed -ensures that a known, or official, version of the software is always -available and in working order. - -```{note} -Avoiding making edits directly on the `main` branch is considered best practice for most workflows and projects! -``` - -### Working with the Git Feature Branch Workflow - -This model assumes a single, remote GitHub repository with a branch -named `main`, that contains the official version of all of the digital -assets, along with a history of all of the changes made. When a -contributor wishes to make changes to the remote repository, they -clone the repo and create a descriptively named feature branch, -such as `my-new-feature` or perhaps `issue-nnn`, where `nnn` is the -number of an issue opened on the repository that this new feature -branch will address. Changes by the contributor are then made to -the feature branch in a local copy of the repository. When ready, -the new branch is pushed to the remote repository. - -At this point, -the new branch can be viewed, discussed, and even changed by -contributors with write access to the remote repository. When the -author of the feature branch thinks the changes are ready to be -merged into `main` on the remote repository, they create a PR. The -PR signals the project maintainers that the contributor would like -to merge their feature branch into `main`, and invites review of the -changes made in the branch. GitHub simplifies the process of viewing -the changes by offering a variety of ways to see context differences -(diffs) between `main` and the feature branch. Discussion between -the reviewers and the contributor inside a PR discussion forum -occurs in the same way that discussion over GitHub [Issues](github-issues) takes -place inside a discussion forum associated with a particular issue. -If additional changes are requested by the reviewers, these can be -made by the contributor in their local repository, committed, and -then pushed to the remote using the same processes they used with -the initial push. Once reviewers are satisfied with the changes, a -project maintainer can merge the feature branch with `main`. - -##### Cloning the remote repository - -If you don’t have a local copy of the remote repository, you’ll want -to create one by [cloning the -remote](github-cloning-forking) -to your local computer. This can be done with the git command line -tools and the general form of the command looks like this: - -``` -git clone repository-url local-directory-name -``` - -Where `repository-url` is the URL for the GitHub repo that you want -to clone, and `local-directory-name` is the directory path on your -local machine into which you want to create the clone. The local -directory need not already exist. The clone command will create the -local directory for you. If you don’t know the URL for your -repository, navigate your web browser to your GitHub repository, -and click on the `Code` button. The URL will be displayed. - -For example, let's clone the [Project Pythia sandbox repository](https://github.com/ProjectPythia/github-sandbox): - -``` -git clone https://github.com/ProjectPythia/github-sandbox.git -``` - -Note, we did not specify a `local-directory_name` here, so git will -use the `base name` of the `repository_url`, "github-sandbox" as -the local directory. - -##### Start with the main branch - -Continuing with our example above, make sure you are on the main -branch and that it is up to date with the remote repository main: - -``` -cd github-sandbox -git checkout main -git pull -``` - -You should see output that looks like: - -``` -Already on 'main' -Already up to date. -``` - -Remember you can read more about [GitHub branches](github-branches) in our previous chapter. - -##### Create a new branch - -Create a separate branch for every new capability you work on: - -``` -git checkout -b my-new-feature -``` - -This command will create a new branch named `my-new-feature`, if it -doesn’t exist already, or switch to the existing branch if it does. -Either way, any changes you make will occur in the branch `my-new-feature`, -not in `main`. The output should look something like: - -``` -Switched to a new branch 'my-new-feature' -``` - -##### Make changes and commit - -Next, we'll make changes and commit them to the `my-new-feature branch` in -the local git repository. - -Use your favorite editor to edit the file "sample.py". Add the line: - -``` -print ("Do you like to rock the party?") -``` - -after the existing `print` statement in the file. - -Run the command `git status` and look at the output. You should see -something like: - -``` -On branch my-new-feature -Changes not staged for commit: - (use "git add ..." to update what will be committed) - (use "git restore ..." to discard changes in working directory) - modified: sample.py - -no changes added to commit (use "git add" and/or "git commit -a") -``` - -Another helpful command is `git diff`, which should give output -that looks like: - -``` -diff --git a/sample.py b/sample.py -index b2a3b61..bf89419 100644 ---- a/sample.py -+++ b/sample.py -@@ -1,5 +1,6 @@ - """This is a text file that contains a sample Python script""" - print ("Hello, Python learners!") -+print ("Do you like to rock the party?") - a = 2 - b = 8 -``` - -It's probably obvious that `git status` will show you which files have been modified and are -ready to be committed, while `git diff` will show you how your changes -to `my-new-feature` branch differ from the `main` branch in the local -repository. Once you are ready, commit your changes to the local -repository: - -``` -git add sample.py -git commit -m "having fun yet?" . -``` - -After a successful commit you should see a message like: - -``` -[my-new-feature 69162bc] having fun yet? - 1 file changed, 1 insertion(+) -``` - -##### Push the feature branch to the remote repository - -After running `git commit` your changes have been captured in your -local repository. But most likely only you can see them, and if -your local file system fails your changes may be lost. To make your -changes visible to others, and safely stored on your remote GitHub -repository, you need to push them. However, remember at the beginning -of this section we said that the **Git Feature Branch Workflow** works -when you have write access to the remote repository? Unless you are -a member of Project Pythia you probably don't have write access to -the `github-sandbox` remote repo. So you won't be able to push your -changes to it. That's OK. We can still run the `push` command. It won't -break anything. In the next section on **Forking Workflow** we will -discuss how to make changes on remote repositories that you do NOT -have write access to, such as the one we're using in this example. Here -is the `push` command that we expect to fail: - -``` -git push --set-upstream origin my-new-feature -``` - -You should get a helpful error message like: - -``` -remote: Permission to ProjectPythia/github-sandbox.git denied to clyne. -fatal: unable to access 'https://github.com/ProjectPythia/github-sandbox.git/': The requested URL returned error: 403 - -``` - -The use of the ‘--set-upstream’ option is a one-time operation when -you push a new branch. Later, if you want to push subsequent changes -to the remote you can simply do: - -``` -git push -``` - -If you are feeling unsatisfied about not having `git push` succeed, there -is a simple solution: create a GitHub repository owned by you. The -GitHub Quickstart guide provides an excellent [tutorial](https://docs.github.com/en/get-started/quickstart/create-a-repo) on how to -do this. - -##### Making a Pull Request - -Finally, after cloning a remote repository, creating a feature -branch, making your changes, committing them to your local repository, -and pushing your commits back to the remote repository, you are now -ready to issue a PR requesting that the remote repository maintainers -review your changes for potential merger into the main branch on -the remote. This final action must be performed from within your -web browser. After -navigating to your repo do the following: - -1. Click on “Pull Requests” in the top navigation bar -1. Click on “New Pull Request” -1. Under “Compare changes”, make sure that `base` is set to `main`, and `compare` is set to the name of your feature branch, `my-new-feature` -1. Click on “Create Pull Request” -1. A PR window should open up. Provide a descriptive title, and any helpful comments that you want to communicate with the reviewers -1. Click on “Create Pull Request” in the PR window. - -That’s it! You’re done! Sit back and wait for comments from reviewers. -If changes are requested, simply repeat the steps above. Once your -PR is merged you’ll receive notification from GitHub. - -##### Safety tip on synchronization - -Over time your local repository will diverge from the remote. Before -starting on a new feature, or if the `main` branch on remote may have -been updated while you were working on `my-new-feature`, it is a good -idea to periodically sync up with the remote `main`. Make sure all -of your changes to `my-new-feature` have been committed to the local -repository, and then do: - -``` -git checkout main -git pull -git checkout my-new-feature -git merge main -``` - -## Forking Workflow - -The **Git Feature Branch Workflow** described above, along with the -steps needed to submit a PR, work when you have write access to the -remote repository. But as we saw, if you don't have write access -you will not be able to push your changes to the remote repo. So, -if you are contributing to an open source project, such as Project -Pythia for example, a slightly different workflow is required. -The **Forking Workflow** is the one most commonly used for public open -source projects. The primary difference between the **Forking Workflow** -and the **Git Feature Branch Workflow** is that with the former, two -remote repositories are involved: one managed by the developers of -the project that you wish to contribute to, and one owned by you. -To help keep things clear we will refer to these remotes as the -upstream repository and the personal repository, respectively. Not -surprisingly, the personal repository will be a clone of the project -repository that you own and can push changes too. The personal -repository must be public, so that the maintainers of the upstream -repository can pull changes from it. Other than a couple of additional -steps required at the beginning and the end, the process of submitting -a PR when using the **Forking Workflow** is identical to that of the -**Git Feature Branch Workflow**. The basic steps are as follows: - -1. A contributor _forks_ the upstream repository, creating a remote clone that is owned by the contributor: the personal repository -1. The contributor then clones the newly created personal remote repository, creating a local copy. Yup, that is two clones. -1. The contributor creates a new branch in their local repository -1. The contributor makes changes to the branch and commits them to their local repository -1. The contributor pushes the branch to their personal remote repository that was created in step 1 -1. The contributor submits a PR via GitHub to the upstream repository - -Note that steps 2 through 5 are identical to steps 1 through 4 for -the **Git Feature Branch Workflow**. Hence, here we only discuss the -first step, and last step. - -### Forking the upstream repository - -GitHub makes it really easy to fork a remote repository. Simply -navigate your web browser to the upstream repository that you want -to fork, and click on Fork. GitHub will create a clone of the -upstream repository in the remote destination selected by you on -GitHub, and will then redirect your browser to the newly created -forked, personal repository. The personal repository is owned by -you. Any changes made here will not impact the upstream repository -until you are ready to submit a PR. Let's try it. Follow -the steps under Forking a repository [here](github-cloning-forking). - -### Clone, branch, change, commit, push - -The next steps are the same as described above for the **Git Feature -Branch Workflow**. Clone a local copy of the newly created remote, -personal repository, create a feature branch, make your changes, -commit your changes, and push the new branch with your commits to your personal repository. - -### Making a Pull Request - -Once the new feature branch has been pushed to the contributor’s -personal repository, a PR can be created that asks the maintainers -of the upstream repository to merge the contents of the feature -branch on the contributor’s repository into the main branch on the -upstream repository. This step is remarkably similar to making a -PR in the **Git Feature Branch Workflow**. The only difference is that -the contributor navigates their browser to the upstream, remote -repository, not the personal remote, and initiates the PR there. -Specifically, the following steps are once again followed, but -performed on the upstream remote: - -1. Click on “Pull Requests” in the top navigation bar -1. Click on “New Pull Request” -1. Under “Compare changes”, make sure that `base` is set to `main`, and `compare` is set to the name of your feature branch, `my-new-feature` -1. Click on “Create Pull Request” -1. A PR window should open up. Provide a descriptive title, and any helpful comments that you want to communicate with the reviewers -1. Click on “Create Pull Request” in the PR window. - -### Safety tip on synchronization - -Just like with the **Git Feature Branch Workflow** model, over time -your local repository will diverge from the remote(s). Before -starting on a new feature, or if the main branch on remote may have -been updated while you were working on `my-new-feature`, it is a good -idea to periodically sync up with the remote `main`. When working -with forks things get a little more complicated than when only a -single remote is involved. Before syncing with the upstream remote -you must first configure your local repository by running the -following commands from within your local copy of the repo: - -``` -git remote -v -``` - -This should produce an output that looks similar to the following: - -origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) -origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push) - -Next, specify a new remote upstream repository that will be synced with the fork. - -``` -git remote add upstream upstream-url -``` - -Where `upstream-url` is the URL of the upstream repository. - -Finally, rerun the `git remote -v` command and you should see output -that looks like this: - -``` -origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) -origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push) -upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch) -upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push) -``` - -After performing the above steps, you can then synchronize your -local repository with the upstream remote by running the following: - -``` -git fetch upstream -git checkout main -git merge upstream/main -``` - ---- - -## Summary - -- The steps that lead up to - the PR depend your GitHub Workflow. -- Two commonly used GitHub Worflows are **Git Feature Branch Workflow** and - **Forking Workflow**. The former is appropriate for teams of collaborators - where everyone has write access to the GitHub repository. The latter - is commonly used when a developer wishes to contribute to a public GitHub - project for which they do not have write access to the repository. - -### What's Next? - -In the next lesson we will put the **Forking Workflow** to work and show you -how to use it to [contribute to Project Pythia](contribute-to-pythia). - -## References - -1. Atlassian's tutorial on [workflows](https://www.atlassian.com/git/tutorials/comparing-workflows) -1. GitHub's [Collaborating with Pull Requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) diff --git a/_preview/419/_sources/foundations/github/review-pr.md b/_preview/419/_sources/foundations/github/review-pr.md deleted file mode 100644 index 52ef0bc91..000000000 --- a/_preview/419/_sources/foundations/github/review-pr.md +++ /dev/null @@ -1,127 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# Reviewing Pull Requests - -Pull Requests (PRs) are typically reviewed by collaborators before being merged in to the main project branch. Many people feel overwhelmed, or feel as though their skills are lacking, when asked to perform their first PR review. If you find yourself in this or a similar situation, the examples in this tutorial can be quite helpful. With the help of this tutorial, anyone can quickly learn the basics of reviewing PRs, which can boost collaboration and productivity in any project hosted on GitHub. This tutorial also contains useful tips on how to effectively review a PR in many different situations. - -## Overview: - -This tutorial covers the following topics: - -1. What is a Pull Request Review? -1. Requesting Pull Request Reviews -1. Ways to View a Pull Request -1. Providing a Pull Request Review -1. What to Look for When Reviewing - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------- | ----------- | ----- | -| [What is GitHub](what-is-github) | Necessary | | -| [GitHub Repositories](github-repos) | Necessary | | -| [Cloning and Forking](github-cloning-forking) | Necessary | | -| [Basic Version Control with _git_](basic-git) | Necessary | | -| [Issues and Discussions](github-issues) | Recommended | | -| [Branches](git-branches) | Necessary | | -| [Pull Requests](github-pull-request) | Necessary | | - -- **Time to learn**: 30 minutes - ---- - -## What is a Pull Request Review? - -A PR Review is an opportunity for a team member to look through proposed file changes and request changes before merging these changes into the primary project branch (usually called "main"), or another important project branch. The reviewer may attempt to acquire information about the content of the PR by asking precise questions. They may also suggest edits to the content, either explicitly, such as changes to specific lines of code, or implicitly, such as a request for more detailed documentation. Before the PR is merged, the author of the PR content should attempt to satisfy all requests in the review. In fact, if the branch being updated by the PR has active protections, the author may be required to satisfy some such requests. - -## Requesting Pull Request Reviews - -Most people learning GitHub are confused about when to request review on a PR they create. The answer is that review should be requested when a PR is (or is likely) ready to merge into the primary project branch (or another important project branch). - -To start the review process, navigate to the right sidebar menu that appears when viewing your PR. Then, under "Reviewers", select the gear icon, and then select or enter a GitHub user's ID for whom you would like to approve your work. If the files listed in the PR are owned or recently edited by specific reviewers, GitHub may automatically suggest the user IDs of those reviewers. - -Request Reviews
- -```{admonition} Did you know? -:class: info -It is possible to automate this process with a `CODEOWNERS` file and [GitHub actions](https://docs.github.com/en/actions). -``` - -To learn more about any topic relating to requesting a PR review, including topics such as CODEOWNERS files, please review the official [Requesting a Pull Request Review Documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review). - -## Ways to View a Pull Request - -If you are unfamiliar with the process of reviewing a PR, the material in this tutorial section will describe the process in detail. The first step to reviewing a PR is to review the files changed by the PR. However, before reviewing the changed files, it is very helpful to view these files in a meaningful way. - -The first useful way to view changed files in a PR is through the PR's "Files Changed" tab. On this tab, added content is displayed in green, while removed content is displayed in red. - -Reviewing Files Changed
- -This method of viewing changed files works well for most types of code; however, if the code is designed to be rendered as a webpage, Jupyter Notebook, or other similar format, a different method of viewing is recommended. - -There are some standard methods of easily viewing Jupyter Notebooks and rendered webpages in GitHub; these are commonly used by repositories with large amounts of this type of content. GitHub actions can be used to provide previews of the rendered content; there are also third-party services, such as [ReviewNB](https://www.reviewnb.com/), that allow for viewing of this content. Also, it is important to know that when viewing a preview of webpage content provided by GitHub actions, using any absolute links in the preview will take the web browser out of the preview and out of GitHub. - -Another popular way to easily view any type of PR content is to locally check out the PR branch. This can be accomplished by cloning the GitHub repo and switching in the local clone to the branch containing the PR. Viewing a PR through a local clone allows the reviewer to use any applications available through the terminal, including code editors, Jupyter applications, and Web browsers, to view the changed files quickly and easily. For more information on this process, please review the [documentation GitHub provides](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally) on checking out pull requests locally. - -As described above, there are many ways to view changed files in a GitHub PR, including local clones, GitHub action previews, and services such as ReviewNB. However, these services may not detail the changes to the files listed in the PR; therefore, the "Files Changed" tab should be the main resource for deciding where to focus a review. - -## Providing a Pull Request Review - -There are many ways to provide a PR review. The most basic of these is to comment on specific lines. This type of review can be performed through the "Files Changed" tab. By clicking on the "+" icon next to a line of code, the reviewer can provide a comment, and either start a new review, or simply link the comment to the line of code. - -Inline Reviews
- -If the review consists mainly of comments relevant to specific lines of code, this review method is preferred. - -If you are the reviewer, and the review consists mainly of small edits that you can perform yourself, this is also the preferred review method. To start one of these small edits, open a comment on the line of code to be edited, as described above. You can then suggest the edit by clicking on the "+-" icon, circled in red in the screenshot below. This icon automatically populates the comment box with the line of code and formats it with Markdown. You must replace the line of code in the comment box with the edited version, then link the comment or start a new review as described above.
- -Review Suggestions
- -If the review is more complex than simple edits to specific lines of code, you can find more detailed reviewing tools in the Review Changes menu in the top right. This menu contains a comment box, as well as options for specific types of review. These options are described in detail after the informational screenshot below. - -Approving Review
- -- The "Comment" option allows the reviewer to provide simple comments or questions on the PR before the review is finished and the PR merged. Please note that comments and questions that may hinder the PR merge process should not be handled in this way. - -- The "Approve" option is used to indicate that the reviewer wholeheartedly approves the content changes in the PR, and that these content changes should be merged into an important project branch as quickly as possible. This option is also known as the LGTM (let's get this merged) option. - -- The "Request changes" option is used to indicate that the content changes contain one or more elements that require improvement or resolution before the PR can be merged. - -After providing review text in the comment box, and selecting a review type, make sure to click on the "Submit Review" button to finish the review. - -## What to Look for When Reviewing - -There are specific elements of PRs that are more commonly prioritized during a review. To address these elements, most reviewers perform the following tasks: - -- Look at the description and linked GitHub issue to make sure the PR addresses the issue -- Attempt to figure out the details of the content changes in the PR, and the purpose of those changes -- Look at the content for spelling errors -- Provide feedback on the code itself - - Does the code contain input checks, debug statements, verification, or the like? - - If the code contains any of these checks, are they sufficiently robust? - - Is the code written in a way that allows for understanding of its purpose? - - As the reviewer, are you familiar with a way to simplify the code, or make the code more efficient? - - Does the code contain identifiers with conflicting or confusing names that need correcting? - - Do you, as the reviewer, notice any other issue with the code that may need to be dealt with in your review? -- If any of the content changed by the PR is meant to be rendered (e.g., as a webpage or Jupyter Notebook), preview this content to check for issues with design and functionality -- Finally, try to clearly state not only the changes made in your review, but also the issues not changed by your review. It is perfectly acceptable to not cover every item in this list; however, it is good practice to include the items covered in the review, and the nature of these changes. Most teams that manage a GitHub repository appreciate the inclusion of opinion and detail in a PR review. - ---- - -## Summary - -- PR Reviews safeguard the primary project branch (and other important project branches) in a GitHub repository. These reviews require contributors in a repository to perform a detailed examination of changes to code and other files. The files remain unchanged until these examinations are finished. -- There exist certain standards pertaining to PR reviews; in addition to following these standards, it is important to provide detail on the basis of your review. - -### What's Next? - -The next tutorial will cover standards and other details about [GitHub Workflows](github-workflows). - -## Resources and References - -1. GitHub's tutorial on [Collaborating with Pull Requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) -2. GitHub's tutorial on [Requesting a Pull Request Review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) -3. GitHub's tutorial on [Checking Out Pull Requests Locally](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally) diff --git a/_preview/419/_sources/foundations/github/what-is-github.md b/_preview/419/_sources/foundations/github/what-is-github.md deleted file mode 100644 index c09073f22..000000000 --- a/_preview/419/_sources/foundations/github/what-is-github.md +++ /dev/null @@ -1,135 +0,0 @@ -```{image} ../../images/GitHub-logo.png -:alt: GitHub Logo -:width: 400px -``` - -# What is GitHub? - -## Overview: - -1. What is GitHub? -1. No experience necessary! -1. Free and open-source software (FOSS) -1. Version control systems (VCS) -1. GitHub = FOSS + VCS + Web -1. Register for a free GitHub account - -## Prerequisites - -| Concepts | Importance | Notes | -| -------- | ---------- | ----- | -| None | | | - -- **Time to learn**: 15 minutes - ---- - -## What is GitHub? - -[GitHub](https://github.com) is a web-based platform for the dissemination of free and open-source software. - -If you are reading this lesson, you are already using GitHub, as that is where Project Pythia hosts its content! - -GitHub provides the following: - -1. _Version control_ for free and open-source software and other digital assets -1. Project _discussion forums_ -1. _DevOps_ to facilitate building and testing software -1. _Bug_ reporting, patching, and tracking -1. _Documentation_ hosting -1. An environment that fosters _collaboration_ - -Although GitHub can host any digital asset, the most common use case for GitHub is for individuals or organizations to house _repositories_ of _free_ and _open-source software_: - -## No experience necessary! - -You do not need to be an experienced software developer or be proficient in version control to make use of GitHub! Perhaps, though, you have used a particular package (e.g., Xarray or Matplotlib) and have had questions about its usage, noticed a bug, or had an idea for a new feature for the package! You can participate in a project's development via GitHub the same way you might have interacted with its developers via email in the past. - -## Free and open-source software (FOSS) - -Much of what we term the _scientific Python software ecosystem_ consists of _free and open-source software_. Often abbreviated as **FOSS**, this means: - -1. The software is free of charge, and -1. The various files which contain the _software code_ are publicly available. - -```{admonition} Did you know? -:class: info -The [Python language](https://python.org) itself is an example of *FOSS*! -``` - -FOSS is nothing new. For example, the [Linux kernel source code](https://kernel.org) has been available to download for many years. - -```{admonition} Free $\neq$ open source! -:class: tip -Just because a software package may be free does not mean that its source code is open! For example, although Nvidia makes its video drivers available for free download, the source code for those drivers is proprietary. -``` - -Arguably, the greatest advantage of open-source software is that it enables _collaborative sharing_, and thus community feedback. - -Types of community input may include the following: - -1. _Issues_: usage questions, bug reports, feature requests -1. _Pull requests_: a user can ask that that their changes/additions be incorporated into the project -1. _Discussions_: a community forum on the open source project - -## Version control systems (VCS) - -We will discuss version control in more detail later in this series, but the need to track and manage changes to a project, especially one that involves software, has long been known. Over the years, FOSS developers have used VCS such as _cvs_, _svn_, and most recently, _git_. All of these systems are _command-line tools_. - -## FOSS and VCS on the Internet - -A successful FOSS project needs to be accessible via the web. As mentioned before, the Linux kernel and the Python language have long been available using first-generation remote access protocols such as FTP and HTTP, and SSH. Later, VCS tools such as cvs and svn established their own TCP protocols for remote access. With the advent of _git_, web-based services that supported HTTP(S) and SSH sprung up. Each of these VCS leverages the concept of a particular FOSS project as a code repository. - -```{admonition} Did you know? -:class: info -Linus Torvalds, the original developer (and still the lead maintainer) of **Linux**, is also the original developer of [Git](https://git-scm.com)! -``` - -```{admonition} Stay tuned! -:class: tip -We will discuss version control and the use of **Git** via the command line later in this series. -``` - -## FOSS + VCS + Web = GitHub - -Perhaps the most popular web-based platform that uses Git for FOSS VCS is [GitHub](https://github.com). GitHub hosts all of the Python software packages that Project Pythia covers as code repositories (we'll use the term Git repo, or more generally just repo henceforth to represent a GitHub code repository). - -For example, here is a screenshot from [Xarray's GitHub](https://github.com/pydata/xarray) Git repo: - -Xarray GitHub - -```{note} -The above screenshot is from one moment in time. When you visit the Xarray GitHub link above, it will no doubt look different! -``` - -## Register for a free GitHub account - -While one can freely browse GitHub repositories such as Xarray anonymously, it's necessary to log into a unique (and free) user account in order to take advantage of GitHub's full capabilities, such as: - -1. Opening Issues and Pull Requests -1. Participating in Discussions -1. Hosting your own repository - -Your next step (if you haven't already) should be to register for your free GitHub account. As with many online services, you will specify a user ID, password, and email address to use with your account. - -To do so, simply point your browser to the [GitHub sign-up page](https://github.com/join): - -GitHub Signup - -While GitHub offers paid options, a free account is typically all that is needed! - ---- - -## Summary - -- GitHub serves as a web-based platform for digital assets, particularly FOSS. -- GitHub uses Git as its version control system. -- You can set up a free user account on GitHub. - -### What's Next? - -In the next lesson, we will explore some GitHub repositories. - -## References - -1. [GitHub (Wikipedia)](https://en.wikipedia.org/wiki/GitHub) diff --git a/_preview/419/_sources/foundations/how-to-run-python.md b/_preview/419/_sources/foundations/how-to-run-python.md deleted file mode 100644 index c0fd2116e..000000000 --- a/_preview/419/_sources/foundations/how-to-run-python.md +++ /dev/null @@ -1,80 +0,0 @@ -# Installing and Running Python - ---- - -## Overview - -This section provides an overview of different ways to run Python code, and quickstart guides for: - -1. Choosing a Python platform -2. Installing and managing Python with Conda - -## Prerequisites - -| Concepts | Importance | Notes | -| -------------------------------------------------------------------------------- | ---------- | ----- | -| [Why Python?](https://foundations.projectpythia.org/foundations/why-python.html) | Helpful | | - -- **Time to learn**: 20 minutes - ---- - -## Choosing a Python Platform - -There is no single official platform for the Python language. Here we provide a brief rundown of 3 popular platforms: - -1. The terminal, -2. Jupyter notebooks, and -3. IDEs (integrated development environments). - -Here we hope to provide you with enough information to understand the differences and similarities between each platform, so that you can make the best choice for your work environment and learn along effectively, regardless of your Python platform preference. - -In general, it is always best to test your programs in the same environment in which they will be run. The biggest factors to consider when choosing your platform are: - -- What are you already comfortable with? -- What are the people around you using (peers, coworkers, instructors, etc.)? - -### Terminal - -For learners who are familiar with basic [Linux commands](https://cheatography.com/davechild/cheat-sheets/linux-command-line/) and text editors (such as Vim or Nano), running Python in the terminal is the quickest route straight to learning Python syntax without the covering the bells and whistles of a new platform. If you are running Python on a supercomputer, through an HTTP request or SSH tunneling, you might want to consider learning in the terminal. - -[How to Run Python in the Terminal](terminal.md) - -### Jupyter Notebooks - -We highly encourage the use of Jupyter notebooks: a free, open-source, interactive tool running inside a web browser that allows you to run Python code in "cells." This means that your workflow can alternate between code, output, and even Markdown-formatted explanatory sections that create an easy-to-follow analysis or "computational narrative" from start to finish. Jupyter notebooks are a great option for presentations or learning tools. For these reasons, Jupyter is very popular among scientists. Most lessons in this book will be taught via Jupyter notebooks. - -[How to Run Python in a Jupyter Session](jupyter.md) - -### Other IDEs - -If you code in other languages, you might already have a favorite IDE that will work just as well in Python. [Spyder](https://www.spyder-ide.org) is a Python specific IDE that comes with the [Anaconda download](https://www.anaconda.com/products/distribution). It is perhaps the most familiar IDE if you are coming from languages such as [Matlab](https://www.mathworks.com/products/matlab.html) that have a language specific platform and display a list of variables. [PyCharm](https://www.jetbrains.com/pycharm/) and [Visual Studio Code](https://code.visualstudio.com) are also popular IDEs. Many IDEs offer support for terminal execution, scripts, and Jupyter display. To learn about your specific IDE, visit its official documentation. - -_We recommend eventually learning how to develop and run Python code in each of these platforms._ - -## Installing and managing Python with Conda - -Conda is an open-source, cross-platform, language-agnostic package manager and environment management system that allows you to quickly install, run, and update packages within your work environment(s). Conda is a vital component of the Python ecosystem. Understanding it is important, regardless of the platform you chose to run your Python code. - -[Learn more about Conda here](conda.md) - ---- - -## Summary - -Python can be run on many different platforms. You may choose where to run Python based on a number of factors. The tutorials in this book will be formatted as Jupyter Notebooks. - -### What's Next? - -- [How to Run Python in the Terminal](terminal.md) -- [How to Run Python in a Jupyter Session](jupyter.md) -- [Learn more about Conda here](conda.md) - -## Resources and References - -- [Linux commands](https://cheatography.com/davechild/cheat-sheets/linux-command-line/) -- [Spyder](https://www.spyder-ide.org) -- [Anaconda](https://www.anaconda.com/products/distribution) -- [Matlab](https://www.mathworks.com/products/matlab.html) -- [PyCharm](https://www.jetbrains.com/pycharm/) -- [Visual Studio Code](https://code.visualstudio.com) diff --git a/_preview/419/_sources/foundations/jupyter.md b/_preview/419/_sources/foundations/jupyter.md deleted file mode 100644 index 9fd45a415..000000000 --- a/_preview/419/_sources/foundations/jupyter.md +++ /dev/null @@ -1,115 +0,0 @@ -# Python in Jupyter - ---- - -## Overview - -You'd like to learn to run Python in a Jupyter session. Here we will cover: - -1. Installing Python in Jupyter -2. Running Python code in Jupyter -3. Saving your notebook and exiting - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------------------------------------------------------------------- | ---------- | ----- | -| [Installing and Running Python](https://foundations.projectpythia.org/foundations/how-to-run-python.html) | Helpful | | - -- **Time to learn**: 20 minutes - ---- - -## Installing Python in Jupyter - -To run a Jupyter session, you will need to install some necessary packages into your Conda environment. - -Install `miniconda` by following the [instructions for your machine](https://docs.conda.io/en/latest/miniconda.html). - -[Learn more about Conda here](conda.md) - -Next, create a Conda environment with Jupyter Lab installed. In the terminal, type: - -``` -$ conda create --name pythia_foundations_env jupyterlab -``` - -Test that you have installed everything correctly by first activating your environment and then launching a Jupyter Lab session: - -``` -$ conda activate pythia_foundations_env -$ jupyter lab -``` - -Or you can install the full [Anaconda](https://www.anaconda.com/products/distribution), and select **LAUNCH** under the Jupyter panel in the GUI. - -![Anaconda Navigator](../images/Anaconda.png) - -In both methods, a new window should open automatically in your default browser. You can change the browser when launching from the terminal with (for example): - -``` -jupyter lab —browser=chrome -``` - -## Running Python in Jupyter - -1. With your Conda environment activated and Jupyter session launched (see above), create a directory to store our work. Let's call it `pythia-foundations`. - - ![Jupyter GUI](../images/jupyter_gui.png) - - You can do this in the GUI left sidebar by clicking the new-folder icon. If you prefer to use the command line, you can access a terminal by clicking the icon under the "Other" heading in the Launcher. - -2. Create a new `mysci.ipynb` file within the `pythia-foundations` folder: - - Do this in the GUI on the left sidebar by clicking the "+" icon. - - This will open a new launcher window where you can select a Python kernel under the "Notebooks" heading for your project. _You should see "Python 3" as in the screenshot above._ Depending on the details of your system, you might see some additional buttons with different kernels. - - Selecting a kernel will open a Jupyter notebook instance and add an untitled file to the left sidebar navigator, which you can then rename to `mysci.ipynb`. - - Select "Python 3" to use the Python version you just installed in the `pythia_foundations_env` conda environment. - -3. Change the first notebook cell to include the classic first command: printing, "Hello, world!". - - ```python - print("Hello, world!") - ``` - -4. Run your cell with {kbd}`Shift`\+{kbd}`Enter` and see that the results are printed below the cell. - - ![Jupyter - Hello World](../images/mysci.png) - -**Congratulations!** You have just set up your first Python environment and run your first Python code in a Jupyter notebook. - -## Saving your notebook and exiting - -When you are done with your work, it is time to save and exit. - -To save your file, you can click the disc icon in the upper left Jupyter toolbar or use keyboard shortcuts. - -Jupyter allows you to close the browser tab without shutting down the server. When you're done working on your notebook, _it's important to **click the "Shutdown" button** on the dashboard_ to free up memory, especially on a shared system. - -Then you can quit Jupyter by: - -- clicking the "Quit" button on the top right, or -- typing `exit` into the terminal - -Alternatively you can simultaneously shutdown and exit the Jupyter session by typing -{kbd}`Ctrl`\+{kbd}`C` in the terminal and confirming that you do want to -"shutdown this notebook server." - ---- - -## Summary - -Jupyter notebooks are a free, open-source, interactive tool running inside a web browser that allows you to run Python code in "cells." To run a Jupyter session you will need to install `jupyterlab` into your Conda environment. Jupyter sessions need to be shutdown, not just exited. - -### What's Next? - -- [How to Run Python in the Terminal](terminal.md) -- [Learn more about Conda here](conda.md) -- [Getting Started with Jupyter](getting-started-jupyter) - -## Resources and References - -- [Anaconda](https://www.anaconda.com/products/distribution) diff --git a/_preview/419/_sources/foundations/jupyterlab.ipynb b/_preview/419/_sources/foundations/jupyterlab.ipynb deleted file mode 100644 index 1c0446067..000000000 --- a/_preview/419/_sources/foundations/jupyterlab.ipynb +++ /dev/null @@ -1,620 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "hC-731NWXAnQ" - }, - "source": [ - "# JupyterLab" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3rIfwtTKpQLf" - }, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MkdbWkKzfegV", - "jp-MarkdownHeadingCollapsed": true, - "tags": [] - }, - "source": [ - "## Overview\n", - "\n", - "JupyterLab is a popular web application on which users can create and write their Jupyter Notebooks, as well as explore data, install software, etc. This section will introduce the JupyterLab interface and cover details of JupyterLab Notebooks.\n", - "\n", - "1. Set Up\n", - "2. The JupyterLab Interface\n", - "3. Running JupyterLab Notebooks" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "yt-WtTrcpRpo" - }, - "source": [ - "## Prerequisites\n", - "| Concepts | Importance | Notes |\n", - "| --- | --- | --- |\n", - "| [Getting Started with Jupyter](getting-started-jupyter) | Helpful | |\n", - "| [Installing and Running Python: Python in Jupyter](https://foundations.projectpythia.org/foundations/jupyter.html) | Helpful | |\n", - "\n", - "- **Time to learn**: 50 minutes" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nvLyiIa8pTI5" - }, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eRjK14BjeQXA", - "tags": [] - }, - "source": [ - "## Set Up\n", - "\n", - "To launch the JupyterLab interface in your browser, follow the instructions in [Installing and Running Python: Python in Jupyter](https://foundations.projectpythia.org/foundations/jupyter.html).\n", - "\n", - "If, instead, you want to follow along using a provided remote JupyterLab instance, launch this notebook via [Binder](https://mybinder.org/) using the launch icon at the top of this page,\n", - "\n", - "![Binder Launch](../images/binder-highlight.png \"Binder launch button location\")\n", - "\n", - "and follow along from there! If launching Binder, take note of the `Launcher` tab in the upper-left (see interface below). Click there to find yourself in the same interface moving forward, and feel free to refer back to this tab to follow along." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Qe2fbSa2gS2X" - }, - "source": [ - "## The JupyterLab Interface\n", - "\n", - "Go to your browser and take a look at the JupyterLab interface.\n", - "\n", - "With a base installation of JupyterLab your screen should look similar to the image below.\n", - "\n", - "Notice:\n", - "- The **Menu Bar** at the top of the screen, containing the typical dropdown menus: \"File\", \"Edit\", \"View\", etc.\n", - "- Below that is the **Workspace Area** (currently contains the Launcher).\n", - "- The **Launcher** is a quick shortcut for accessing the Console/Terminal, for creating new Notebooks, or for creating new Text or Markdown files.\n", - "- On the left is a **Collapsible Sidebar**. It currently contains the File Browser, but you can also select the Running Tabs and Kernels, the Table of Contents, and the Extensions Manager.\n", - "- Below everything is the **Information Bar**, which is context sensitive. We will touch on this more.\n", - "\n", - "![Interface](../images/interface_labeled.png)\n", - "\n", - "We will now take a closer look at certain aspects and features of the JupyterLab Interface." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zpDZFM3ngsY6" - }, - "source": [ - "### Left Sidebar\n", - "\n", - "The Collapsible Left Sidebar is open to the **File Browser Tab** at launch. Clicking the File Browser Tab will collapse the sidebar or reopen it to this tab.\n", - "- Within this tab, you will see the \"+\" button, which allows you to create a new launcher. \n", - "- Next to that is the \"+ folder\" button which allows you to create a new folder that then appears below \"Name\" in the contents of your directory. Double click the folder to enter it, right click the folder for options, or press the \"root folder\" icon to return to the root directory. The root directory is the directory from which JupyterLab was launched. You cannot go above the root directory. \n", - "- The \"upload\" button (looks like an arrow pointing up) allows you to upload files to the current folder. \n", - "- The \"refresh\" button refreshes the File Browser.\n", - "\n", - "Below the File Browser Tab is the **Running Tabs and Kernels Tab**. Currently, this tab doesn't have much in it. We will revisit this when we have running kernels. Remember that Kernels are background processes, so closing a tab (Terminal or Notebook) doesn’t shut down the kernel. You have to do that manually. \n", - "\n", - " \n", - "\n", - "\n", - "The **Table of Contents Tab** is auto-populated based on the headings and subheadings used in the Markdown cells of your notebook. It allows you to quickly jump between sections of the document.\n", - "\n", - " \n", - "\n", - "Last is the **Extensions Manager Tab** where you can customize or enhance any part of JupyterLab. These customizations could pertain to themes or keyboard shortcuts, for example. We will not be covering JupyterLab extensions, but you can read more about them [here](https://jupyterlab.readthedocs.io/en/stable/user/extensions.html).\n", - "\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "1WPljwIRg1KH" - }, - "source": [ - "### Terminals\n", - " \n", - "Let’s select the Running Tabs and Kernels Tab in the Left Sidebar and see how it changes when we've used the Launcher.\n", - "\n", - "Open a Terminal in the Launcher. It should look very similar to the desktop terminal that you initially launched JupyterLab from, but is running from within JupyterLab, within your existing Conda environment, and within the directory you launched JupyterLab from (the same root folder shown in the File Browser Tab). Notice that there is now a Terminal listed in the Running Terminals Tab.\n", - "\n", - "In the terminal you can use your usual terminal commands. For example, in the terminal window, run: \n", - "\n", - "```\n", - "$ mkdir test\n", - "```\n", - "\n", - "Select the File Browser Tab, refresh it, and see that your new folder is there.\n", - "\n", - "In the Terminal Window run:\n", - "\n", - "```\n", - "$ rmdir test\n", - "```\n", - "Hit refresh in the File Browser again to see that the directory is gone.\n", - "\n", - "![Terminal](../images/terminal.png)\n", - "\n", - "Back with the Running Terminals and Kernels Tab open, click the \"X\" in your workspace to close the Terminal window. Notice that the Terminal is still running in the background! Click on the terminal in the Running Terminals and Kernels Tab to reopen it (and hit enter or return to get your prompt back). To truly close it, execute in the Terminal window:\n", - "\n", - "```\n", - "$ exit\n", - "```\n", - "\n", - "OR click the “X” shut down button in the Running Terminals tab.\n", - "\n", - "Doing so will return you to the Launcher. \n", - "\n", - "
\n", - "

Info

\n", - " The terminal is running on the local host when JupyterLab is launched locally, and remote host when invoked through Jupyter Hub.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iQAVvbtJg6pi" - }, - "source": [ - "### Consoles\n", - "\n", - "Back in the Launcher, click the “Python 3” Console button. There is only one console option right now, but you could install more kernels into your Conda environment. \n", - "\n", - "There will be three dots while the kernel starts, then what loads looks like an IPython console. This is a place to execute Python commands in a stand alone workspace which is good for testing. Notice that the kernel started in the Running Terminals and Kernels tab!\n", - "\n", - "Start in a “cell” at the bottom of the Console window. Type:\n", - "``` python\n", - "i = 5\n", - "print(i)\n", - "```\n", - "To execute the cell, type Shift+Enter. Notice that the console redisplays the code you wrote, labels it with a number, and displays (prints) the output to the screen. \n", - "\n", - "In the next cell, enter:\n", - "``` python\n", - "s = 'Hello, World!'\n", - "print(f's = {s}')\n", - "s\n", - "```\n", - "\n", - "The first line of this code designates a string `s` with the value \"Hello, World!\", the second line uses f-formatting to print the string, and the final line just calls up `s`. The last line in the cell will always be returned (its value displayed) regardless of whether you called `print`. Type Shift+Enter to execute the cell. Again the output is labeled (this time with a 2), and we see the input code, the printed standard-out statement, and the return statement. The “return value” and the values “printed to screen” are different!\n", - "\n", - "![Console](../images/console.png)\n", - "\n", - "Close the window and shut down the Console in the Running Kernels tab. We’re back to the Launcher again." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "UDCCs37IhK7S" - }, - "source": [ - "### Text Editor\n", - "\n", - "Click on the “Text File” button in the Launcher.\n", - "Select the File Browser tab to see the new file `untitled.txt` you created.\n", - "\n", - "Enter this Python code into the new text file:\n", - "``` python\n", - "s = 'Hello, World!'\n", - "print(s)\n", - "```\n", - "\n", - "You may notice that the file has a dot instead of an \"X\" where you'd close it. This indicates that the file hasn't been saved or has unsaved changes. Save the file (\"command+s\" on Mac, \"control+s\" on Windows, or \"File▶Save Text\").\n", - "\n", - "Go to the File Browser tab, right-click the new file we created and “Rename” it to `hello.py`. Once the extension changes to `.py`, Jupyter recognizes that this is a Python file, and the text editor highlights the code based on Python syntax.\n", - "\n", - "Now, click the “+” button in the File Browser to create a new Launcher. In the Launcher tab, click on the “Terminal” button again to create a terminal. Now you have 2 tabs open: a text editor tab and a terminal tab. Drag the Terminal tab to the right side of the main work area to view both windows simultaneously. Click the File Browser tab to collapse the left sidebar and get more real estate! Alternatively, you could stack the windows one on top of the other.\n", - "\n", - "Run `ls` in the Terminal window to see the text file we just created. Execute `python hello.py` in the Terminal window. See the output in the Terminal window.\n", - "\n", - "![Text Editor](../images/txt-editor.png)\n", - "\n", - "Now, let’s close the Terminal tab and shut down the Terminal in the Running Kernels tab (or execute “exit” in the Terminal itself). You should just have the Text editor window open; now we're ready to look at Jupyter Notebooks.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "b0Zv8pF8e7Y0" - }, - "source": [ - "## Running JupyterLab Notebooks\n", - "\n", - "There are two ways to open a Jupyter Notebook. One way is to select the File Browser Tab, click the \"New Launcher\" button, and select a Python 3 Notebook from the Launcher. Another way is to go to the top Menu Bar and select \"File▶New▶Notebook\". JupyterLab will prompt you with a dialogue box to select the Kernel you want to use in your Notebook. Select the “Python 3” kernel.\n", - "\n", - "If you have the File Browser Tab open, notice you just created a file called `Untitled.ipynb.` You will also have a new window open in the main work area for your new Notebook.\n", - "\n", - "Let’s explore the Notebook interface:\n", - " \n", - "There is a **Toolbar** at the top with buttons that allow you to Save, Create New Cells, Cut/Paste/Copy Cells, Run the Cell, Stop the Kernel, and Refresh the Kernel. There is also a **dropdown menu** to select the kind of cell (Markdown, Raw, or Code). All the way to the right is the name of your Kernel (which you can click to change Kernels) and a **Kernel Status Icon** that indicates if something is being computed by the Kernel (by a dark circle) or not (by an empty circle).\n", - "\n", - "Below the Toolbar is the Notebook itself. You should see an empty cell at the top of the Notebook. This should look similar to the layout of the Console.\n", - "\n", - "The cell can be in 1 of 2 modes: command mode or edit mode.\n", - "If your cell is grayed out and you can’t see a blinking cursor in the cell, then the cell is in command mode. We’ll talk about command mode more later. Click inside the box, and the cell will turn white with a blinking cursor inside it; the cell is now in edit mode. The mode is also listed on the info bar at the bottom of the page. The cell is selected if the blue bar is on the left side of the cell.\n", - "\n", - "![Notebook Interface](../images/notebook-interface_labeled.png)\n", - "\n", - "You may move the Notebook over so you can see your text file at the same time to compare, resizing the Notebook window as needed." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Dl5dbdlPwmLk" - }, - "source": [ - "### Code Cells\n", - "Click inside the first cell of the Notebook to switch the cell to edit mode.\n", - "Enter the following into the cell:\n", - "\n", - "``` python\n", - "print(2+2)\n", - "```\n", - "\n", - "Then type Shift+Enter to execute the cell.\n", - "\n", - "You'll see the output, `4`, printed directly below your code cell. Executing the cell automatically creates a new cell in edit mode below the first.\n", - "\n", - "In this new cell, enter:\n", - "\n", - "``` python\n", - "for i in range(4):\n", - " print(i) \n", - "```\n", - "\n", - "Execute the cell. A Jupyter code cell can run multiple lines of code; each Jupyter code cell can even contain a complete Python program! \n", - "\n", - "To demonstrate how to import code that you have written in a `.py` file, enter the following into the next cell:\n", - "\n", - "``` python\n", - "import hello\n", - "```\n", - "\n", - "Then type Shift+Enter. \n", - "\n", - "This single-line `import` statement runs the contents of your `hello.py` script file, and would do the same for any file regardless of length. \n", - "\n", - "
\n", - "

Warning

\n", - " It is generally considered bad practice to include any output in a “.py” file meant to be imported and used within different Python scripts. Such a file should contain only function and class definitions.\n", - "
\n", - "You've executed the cell with the Python 3 kernel, and it spit out the output, “Hello, World!” Since you've imported the `hello.py` module into the Notebook’s kernel runtime, you can now directly look at the variable “s” in this second cell.\n", - "Enter the following in the next cell:\n", - "\n", - "``` python\n", - "hello.s\n", - "```\n", - "\n", - "Hit Shift+Enter to execute.\n", - "\n", - "Again, it displays the value of the variable `s` from the “hello” module we just created. One difference is that this time the output is given its own label `[2]` matching the input label of the cell (whereas the output from cell `[1]` is not labelled). This is the difference between output sent to the screen vs. the return value of the cell.\n", - "\n", - "Let’s now import a module from the Python standard library:\n", - "\n", - "``` python\n", - "import time\n", - "time.sleep(10)\n", - "```\n", - "\n", - "Again, hit Shift+Enter. \n", - "\n", - "The `time.sleep(10)` function causes code to wait for 10 seconds, which is plenty of time to notice how the cell changes in time:\n", - " - The label of the cell is `[*]`, indicating that the kernel is running that cell \n", - " - In the top right corner of the Notebook, the status icon is a filled-in circle, indicating that the kernel is running\n", - "\n", - "After 10 seconds, the cell completes running, the label is updated to `[3], and the status icon returns to the “empty circle” state. If you rerun the cell, the label will update to `[4]`.\n", - "\n", - "![Code Cells](../images/codecells.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DTSoIqjAM4sg", - "jp-MarkdownHeadingCollapsed": true, - "tags": [] - }, - "source": [ - "### Markdown Cells\n", - "Now, with the next cell selected (i.e., the blue bar appears to the left of the cell), whether in edit or command mode, go up to the “cell type” dropdown menu above and select “Markdown”.\n", - "Notice that the `[ ]` label goes away.\n", - "\n", - "Markdown is a markup language that allows you to format text in a plain-text editor. Here we will demonstrate some common Markdown syntax. You can learn more at [the Markdown Guide site](https://www.markdownguide.org/) or in our [Getting Started with Jupyter: Markdown](https://foundations.projectpythia.org/foundations/markdown.html) content.\n", - "Click on the cell and enter edit mode; we can now type in some markdown text like so:\n", - "\n", - "```markdown\n", - "# This is a heading!\n", - "And this is some text.\n", - "\n", - "## And this is a subheading\n", - "with a bulleted list in it:\n", - "\n", - " - one\n", - " - two\n", - " - three\n", - "```\n", - "\n", - "Then press Shift+Enter to render the markdown to HTML.\n", - "\n", - "\n", - "\n", - "Again, in the next cell, change the cell type to “Markdown”. To demonstrate displaying equations, enter:\n", - "\n", - "```markdown\n", - "## Some math\n", - "\n", - "And Jupyter’s version of markdown can display LaTeX:\n", - "\n", - "$$\n", - "e^x=\\sum_{i=0}^{\\infty} \\frac{1}{i!}x^i\n", - "$$\n", - "```\n", - "\n", - "When you are done, type Shift+Enter to render the markdown document.\n", - "\n", - "\n", - "\n", - "You can also do inline equations with a single \"$\", for example\n", - "\n", - "```markdown\n", - "This is an equation: $i^4$.\n", - "```\n", - "\n", - "\n", - "\n", - "Note that the “markdown” source code is rendered into much prettier text, which we can take advantage of for narrating our work in the Notebook!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DPP39honNHSh", - "jp-MarkdownHeadingCollapsed": true, - "tags": [] - }, - "source": [ - "### Raw Cells\n", - "\n", - "Now in a new cell selected, select “raw” from the “cell type” dropdown menu. Again, the `[ ]` label goes away, and you can enter the following in the cell:\n", - "\n", - "```text\n", - "i = 8\n", - "print(i)\n", - "```\n", - "\n", - "When you Shift+Enter the text isn’t rendered. \n", - "\n", - "This is a way of entering text/source that you do not want the Notebook to do anything with (i.e., no rendering). \n", - "\n", - "![Raw](../images/raw.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "N89Z7vdgNMKx" - }, - "source": [ - "### Command Mode Shortcuts\n", - "\n", - "Now, select the “raw” cell you just created by clicking on the far left of the cell.\n", - "\n", - "You are now in “command mode”. The up and down arrows move to different cells. Don't hit “enter” which would switch the cell to “edit mode.\" Let’s explore command mode.\n", - "\n", - "You can change the cell type with `y` for code, `m` for markdown, or `r` for raw.\n", - "\n", - "You can add a new cell above the selected cell with `a` (or below the selected cell with `b`).\n", - "\n", - "You can cut (`x`), copy (`c`), and paste (`v`).\n", - "\n", - "You can move a cell up or down by clicking and dragging. \n", - "\n", - "
\n", - "

Warning

\n", - " Cells can be executed in any order you want. You just have to select the cell and Shift+Enter, and select the cells in any order you want. However, if you share your notebook, there is an implicit expectation to execute the cells in the order in which they are presented in the notebook. Be careful with this! If variables are reused or redefined between cells, reordering them could have unintended consequences!\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "KySy3X-aauaR" - }, - "source": [ - "### Special Variables\n", - "\n", - "Now, in the empty cell at the end, enter one underscore:\n", - "\n", - "```\n", - "_\n", - "```\n", - "\n", - "This is a special character that means the last cell output. Two underscores means the second to last cell output, and three underscores means the third to last output. You can also refer to the output by label with:\n", - "\n", - "```\n", - "_2\n", - "```\n", - "\n", - "
\n", - "

Danger

\n", - " If the cell you to refer to does not have a return value, this will raise an error.\n", - "
\n", - "\n", - "You can equivalently use the variables `Out[2]` or `In[2]` to retrieve the output and input (as a string).\n", - "\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "F0wU5mU2blds" - }, - "source": [ - "### Shell Commands\n", - "\n", - "In the next code cell, enter the following:\n", - "\n", - "``` ipython\n", - "!pwd\n", - "```\n", - "\n", - "The `!` allows you to write shell commands. A shell command is a command that is run by the host operating system, not the Python kernel. Executing this cell will \"print the working directory\" (i.e. your current directory).\n", - "\n", - "You can even use the output of shell commands as input to Python code. For example:\n", - "\n", - "``` ipython\n", - "files = !ls\n", - "print(files)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "SLtXSO0bNRHC" - }, - "source": [ - "### Stopping & Restarting the Kernel\n", - "\n", - "All of your commands and their output have been remembered by the kernel. However, sometimes you may get code that takes too long to execute, and you need to stop it. For example, in the next code cell, run:\n", - "\n", - "``` python\n", - "time.sleep(1000)\n", - "```\n", - "\n", - "You can stop the kernel with the square “stop” button at the top Notebook toolbar. This results in a \"KeyboardInterrupt\" error.\n", - "\n", - "If you execute a notebook out of order, you can end up in a corrupted state (redefined variables, for example). To start fresh, you should restart the kernel with the circle-arrow button in the toolbar at the top. Now the kernel has forgotten everything, and you'll need to rerun each cell." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wdgosdLGNXtd" - }, - "source": [ - "### Magics\n", - "\n", - "A \"magic\" is a Jupyter Notebook command proceded by a `%` symbol. In the next code cell, enter the following:\n", - "\n", - "``` python\n", - "%timeit time.sleep(1)\n", - "```\n", - "\n", - "The “%timeit” magic is a timer that runs the command multiple times, measuring how long it takes and gathering timing statistics. Hit Shift+Enter to see it work. \n", - "\n", - "Multiline magics work on entire cells, and these have a double-`%`. For example, here is a multiline version of the timeit magic:\n", - "\n", - "``` python\n", - "%%timeit\n", - "\n", - "time.sleep(0.5)\n", - "time.sleep(0.5)\n", - "```\n", - "\n", - "Then press Shift+Enter to run it. This will time the entire cell.\n", - "\n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Y3euwtAoexvZ" - }, - "source": [ - "## Shutting Down\n", - "\n", - "Before shutting down, save your notebook with the disc icon in the Notebook toolbar. Now, close both tabs (the notebook and the text editor). You're back to the Launcher.\n", - "\n", - "The notebook kernel is still running, though, so go to the Running Kernels tab and shut it down.\n", - "\n", - "Now we’re done. Go to “File▶Shut Down” to close both your browser tab and JupyterLab itself." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Pp8SMkeLpfkf" - }, - "source": [ - "## Summary\n", - "\n", - "You are now familiar with the JupyterLab interface and running Jupyter Notebooks. Jupyter is popular for allowing you to intersperse Markdown text or equations between code cells. Jupyter offers some functionality that a Python script does not: certain keyboard shortcuts, special variables, shell scripting, and magics.\n", - "\n", - "### What's next?\n", - "\n", - "- [Markdown](markdown)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "lj8lTb4Aphra" - }, - "source": [ - "## Resources and references\n", - "\n", - "- [Jupyter Documentation](https://jupyter.org/)\n", - "- [JupyterLab Documentation](https://jupyterlab.readthedocs.io/en/stable/)\n", - "- [JupyterLab Extensions](https://jupyterlab.readthedocs.io/en/stable/user/extensions.html)\n", - "- [Markdown Guide](https://www.markdownguide.org/)\n", - "- [Xdev Python Tutorial Seminar Series - Jupyter Notebooks](https://youtu.be/xSzXvwzFsDU)" - ] - } - ], - "metadata": { - "colab": { - "collapsed_sections": [], - "name": "jupyterlab.ipynb", - "provenance": [], - "toc_visible": true - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/_preview/419/_sources/foundations/markdown.md b/_preview/419/_sources/foundations/markdown.md deleted file mode 100644 index 7aa8876b8..000000000 --- a/_preview/419/_sources/foundations/markdown.md +++ /dev/null @@ -1,9 +0,0 @@ -# Formatted Text in the Notebook with Markdown - -```{note} -This content is under construction! -``` - -This section will give a tutorial on formatting text with Markdown: the simple, human-readable text language used extensively in Jupyter notebooks, GitHub Discussions, and elsewhere. - -We will show how this is useful both in notebooks and other places like GitHub Issues. diff --git a/_preview/419/_sources/foundations/overview.md b/_preview/419/_sources/foundations/overview.md deleted file mode 100644 index 85ccdcda4..000000000 --- a/_preview/419/_sources/foundations/overview.md +++ /dev/null @@ -1,12 +0,0 @@ -# Overview - -This section contains cross-referenced tutorial material for foundational computing skills that one needs in order to work effectively with the open-source Scientific Python stack. - -Familiarizing yourself with these topics first will allow a new user to get the most out the Python-specific material in the [Core Scientific Python Packages](../core/overview) section of the book! - -## Topics - -- [Why Python?](why-python): A brief preamble about Python's distinguishing features. -- [Getting started with Python](getting-started-python): A quickstart Python example, followed by detailed tutorials on how to install and run Python on your own system. -- [Getting started with Jupyter](getting-started-jupyter): All about the Jupyter ecosystem, which provides tools and environments for interactive, reproducible computing with Python. -- [Getting started with GitHub](getting-started-github): Learn about the collaboration tools (GitHub) and version control software (git) that enables the open-source community. diff --git a/_preview/419/_sources/foundations/quickstart.ipynb b/_preview/419/_sources/foundations/quickstart.ipynb deleted file mode 100644 index 435acd12f..000000000 --- a/_preview/419/_sources/foundations/quickstart.ipynb +++ /dev/null @@ -1,653 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "5d45c2b9", - "metadata": {}, - "source": [ - "# Quickstart: Zero to Python" - ] - }, - { - "cell_type": "markdown", - "id": "a52c17ad", - "metadata": {}, - "source": [ - "Brand new to Python? Here are some quick examples of what Python code looks like.\n", - "\n", - "This is **not** meant to be a comprehensive Python tutorial, just something to whet your appetite." - ] - }, - { - "cell_type": "markdown", - "id": "84eecdbd", - "metadata": {}, - "source": [ - "## Run this code from your browser!\n", - "\n", - "Of course you can simply read through these examples, but it's more fun to ***run them yourself***:\n", - "\n", - "- Find the **\"Rocket Ship\"** icon, **located near the top-right of this page**. Hover over this icon to see the drop-down menu.\n", - "- Click the `Binder` link from the drop-down menu.\n", - "- This page will open up as a [Jupyter notebook](jupyter.html) in a working Python environment in the cloud.\n", - "- Press Shift+Enter to execute each code cell\n", - "- Feel free to make changes and play around!" - ] - }, - { - "cell_type": "markdown", - "id": "e02bc566", - "metadata": {}, - "source": [ - "## A very first Python program\n", - "\n", - "A Python program can be a single line:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8137f81a", - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Hello interweb\")" - ] - }, - { - "cell_type": "markdown", - "id": "4c7a126b", - "metadata": {}, - "source": [ - "## Loops in Python" - ] - }, - { - "cell_type": "markdown", - "id": "54deab97", - "metadata": {}, - "source": [ - "Let's start by making a `for` loop with some formatted output:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba931e6d", - "metadata": {}, - "outputs": [], - "source": [ - "for n in range(3):\n", - " print(f\"Hello interweb, this is iteration number {n}\")" - ] - }, - { - "cell_type": "markdown", - "id": "42e3baf4", - "metadata": {}, - "source": [ - "A few things to note:\n", - "\n", - "- Python defaults to counting from 0 (like C) rather than from 1 (like Fortran).\n", - "- Function calls in Python always use parentheses: `print()`\n", - "- The colon `:` denotes the beginning of a definition (here of the repeated code under the `for` loop).\n", - "- Code blocks are identified through indentations.\n", - "\n", - "To emphasize this last point, here is an example with a two-line repeated block:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "787f8e09", - "metadata": {}, - "outputs": [], - "source": [ - "for n in range(3):\n", - " print(\"Hello interweb!\")\n", - " print(f\"This is iteration number {n}.\")\n", - "print('And now we are done.')" - ] - }, - { - "cell_type": "markdown", - "id": "cd787438", - "metadata": {}, - "source": [ - "## Basic flow control\n", - "\n", - "Like most languages, Python has an `if` statement for logical decisions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "682eb657", - "metadata": {}, - "outputs": [], - "source": [ - "if n > 2:\n", - " print(\"n is greater than 2!\")\n", - "else:\n", - " print(\"n is not greater than 2!\")" - ] - }, - { - "cell_type": "markdown", - "id": "3a6b8582", - "metadata": {}, - "source": [ - "Python also defines the `True` and `False` logical constants:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b9367b41", - "metadata": {}, - "outputs": [], - "source": [ - "n > 2" - ] - }, - { - "cell_type": "markdown", - "id": "a9164391", - "metadata": {}, - "source": [ - "There's also a `while` statement for conditional looping:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12b978f3", - "metadata": {}, - "outputs": [], - "source": [ - "m = 0\n", - "while m < 3:\n", - " print(f\"This is iteration number {m}.\")\n", - " m += 1\n", - "print(m < 3)" - ] - }, - { - "cell_type": "markdown", - "id": "d5b066ae", - "metadata": {}, - "source": [ - "## Basic Python data types\n", - "\n", - "Python is a very flexible language, and many advanced data types are introduced through packages (more on this below). But some of the basic types include: " - ] - }, - { - "cell_type": "markdown", - "id": "ef3921ed", - "metadata": {}, - "source": [ - "### Integers (`int`)\n", - "\n", - "The number `m` above is a good example. We can use the built-in function `type()` to inspect what we've got in memory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "184f8c79", - "metadata": {}, - "outputs": [], - "source": [ - "print(type(m))" - ] - }, - { - "cell_type": "markdown", - "id": "861da01b", - "metadata": {}, - "source": [ - "### Floating point numbers (`float`)\n", - "\n", - "Floats can be entered in decimal notation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7153a927", - "metadata": {}, - "outputs": [], - "source": [ - "print(type(0.1))" - ] - }, - { - "cell_type": "markdown", - "id": "8545d891", - "metadata": {}, - "source": [ - "or in scientific notation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9bc33c68", - "metadata": {}, - "outputs": [], - "source": [ - "print(type(4e7))" - ] - }, - { - "cell_type": "markdown", - "id": "0a4ceb83", - "metadata": {}, - "source": [ - "where `4e7` is the Pythonic representation of the number $ 4 \\times 10^7 $." - ] - }, - { - "cell_type": "markdown", - "id": "87da5d64", - "metadata": {}, - "source": [ - "### Character strings (`str`)\n", - "\n", - "You can use either single quotes `''` or double quotes `\" \"` to denote a string:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d4cc9304", - "metadata": {}, - "outputs": [], - "source": [ - "print(type(\"orange\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba603039", - "metadata": {}, - "outputs": [], - "source": [ - "print(type('orange'))" - ] - }, - { - "cell_type": "markdown", - "id": "597631d0", - "metadata": {}, - "source": [ - "### Lists\n", - "\n", - "A list is an ordered container of objects denoted by **square brackets**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "be5d040f", - "metadata": {}, - "outputs": [], - "source": [ - "mylist = [0, 1, 1, 2, 3, 5, 8]" - ] - }, - { - "cell_type": "markdown", - "id": "271edb6e", - "metadata": {}, - "source": [ - "Lists are useful for lots of reasons including iteration:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f6d0be16", - "metadata": {}, - "outputs": [], - "source": [ - "for number in mylist:\n", - " print(number)" - ] - }, - { - "cell_type": "markdown", - "id": "f3f8a57e", - "metadata": {}, - "source": [ - "Lists do **not** have to contain all identical types:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f1673dc5", - "metadata": {}, - "outputs": [], - "source": [ - "myweirdlist = [0, 1, 1, \"apple\", 4e7]\n", - "for item in myweirdlist:\n", - " print(type(item))" - ] - }, - { - "cell_type": "markdown", - "id": "c0f192ef", - "metadata": {}, - "source": [ - "This list contains a mix of `int` (integer), `float` (floating point number), and `str` (character string)." - ] - }, - { - "cell_type": "markdown", - "id": "cb662954", - "metadata": {}, - "source": [ - "Because a list is *ordered*, we can access items by integer index:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8178ee98", - "metadata": {}, - "outputs": [], - "source": [ - "myweirdlist[3]" - ] - }, - { - "cell_type": "markdown", - "id": "87e841a7", - "metadata": {}, - "source": [ - "remembering that we start counting from zero!" - ] - }, - { - "cell_type": "markdown", - "id": "2edf60e3", - "metadata": {}, - "source": [ - "Python also allows lists to be created dynamically through *list comprehension* like this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2acae924", - "metadata": {}, - "outputs": [], - "source": [ - "squares = [i**2 for i in range(11)]\n", - "squares" - ] - }, - { - "cell_type": "markdown", - "id": "5c2663a4", - "metadata": {}, - "source": [ - "### Dictionaries (`dict`)\n", - "\n", - "A dictionary is a collection of *labeled objects*. Python uses curly braces `{}` to create dictionaries:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f4dbacdd", - "metadata": {}, - "outputs": [], - "source": [ - "mypet = {\n", - " \"name\": \"Fluffy\",\n", - " \"species\": \"cat\",\n", - " \"age\": 4,\n", - "}\n", - "type(mypet)" - ] - }, - { - "cell_type": "markdown", - "id": "c8e869ca", - "metadata": {}, - "source": [ - "We can then access items in the dictionary by label using square brackets:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "05c66d6e", - "metadata": {}, - "outputs": [], - "source": [ - "mypet[\"species\"]" - ] - }, - { - "cell_type": "markdown", - "id": "b2027506", - "metadata": {}, - "source": [ - "We can iterate through the keys (or labels) of a `dict`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "387dba3d", - "metadata": {}, - "outputs": [], - "source": [ - "for key in mypet:\n", - " print(\"The key is:\", key)\n", - " print(\"The value is:\", mypet[key])" - ] - }, - { - "cell_type": "markdown", - "id": "fb82a430", - "metadata": {}, - "source": [ - "## Arrays of numbers with NumPy\n", - "\n", - "The vast majority of scientific Python code makes use of *packages* that extend the base language in many useful ways.\n", - "\n", - "Almost all scientific computing requires ordered arrays of numbers, and fast methods for manipulating them. That's what [NumPy](../core/numpy.md) does in the Python world.\n", - "\n", - "Using any package requires an `import` statement, and (optionally) a nickname to be used locally, denoted by the keyword `as`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "53ba88cf", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "id": "fb237b33", - "metadata": {}, - "source": [ - "Now all our calls to `numpy` functions will be preceeded by `np.`" - ] - }, - { - "cell_type": "markdown", - "id": "a3391f8d", - "metadata": {}, - "source": [ - "Create a linearly space array of numbers:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ae024132", - "metadata": {}, - "outputs": [], - "source": [ - "# linspace() takes 3 arguments: start, end, total number of points\n", - "numbers = np.linspace(0.0, 1.0, 11)\n", - "numbers" - ] - }, - { - "cell_type": "markdown", - "id": "cc1c2474", - "metadata": {}, - "source": [ - "We've just created a new type of object defined by [NumPy](../core/numpy.md):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c3f3642d", - "metadata": {}, - "outputs": [], - "source": [ - "type(numbers)" - ] - }, - { - "cell_type": "markdown", - "id": "83325c40", - "metadata": {}, - "source": [ - "Do some arithmetic on that array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12cc225d", - "metadata": {}, - "outputs": [], - "source": [ - "numbers + 1" - ] - }, - { - "cell_type": "markdown", - "id": "fee4e53c", - "metadata": {}, - "source": [ - "Sum up all the numbers:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3cef5412", - "metadata": {}, - "outputs": [], - "source": [ - "np.sum(numbers)" - ] - }, - { - "cell_type": "markdown", - "id": "ebf2b669", - "metadata": {}, - "source": [ - "## Some basic graphics with Matplotlib\n", - "\n", - "[Matplotlib](../core/matplotlib.md) is the standard package for producing publication-quality graphics, and works hand-in-hand with [NumPy](../core/numpy.md) arrays.\n", - "\n", - "We usually use the `pyplot` submodule for day-to-day plotting commands:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "76f3329e", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "markdown", - "id": "66033cbc", - "metadata": {}, - "source": [ - "Define some data and make a line plot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32ca697e", - "metadata": {}, - "outputs": [], - "source": [ - "theta = np.linspace(0.0, 360.0)\n", - "sintheta = np.sin(np.deg2rad(theta))\n", - "\n", - "plt.plot(theta, sintheta, label='y = sin(x)', color='purple')\n", - "plt.grid()\n", - "plt.legend()\n", - "plt.xlabel('Degrees')\n", - "plt.title('Our first Pythonic plot', fontsize=14)" - ] - }, - { - "cell_type": "markdown", - "id": "04615882", - "metadata": {}, - "source": [ - "## What now?\n", - "\n", - "That was a whirlwind tour of some basic Python usage. \n", - "\n", - "Read on for more details on how to install and run Python and necessary packages on your own laptop." - ] - }, - { - "cell_type": "markdown", - "id": "edbbb2ad-4544-4564-9750-6d738698fe2e", - "metadata": {}, - "source": [ - "## Resources and references\n", - "\n", - "- [Official Python tutorial (Python Docs)](https://docs.python.org/3/tutorial/index.html)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/_preview/419/_sources/foundations/terminal.md b/_preview/419/_sources/foundations/terminal.md deleted file mode 100644 index 49568964d..000000000 --- a/_preview/419/_sources/foundations/terminal.md +++ /dev/null @@ -1,102 +0,0 @@ -# Python in the Terminal - ---- - -## Overview - -You'd like to learn to run Python in the terminal. Here we will cover: - -1. Installing Python in the terminal -2. Running Python code in the terminal - -## Prerequisites - -| Concepts | Importance | Notes | -| --------------------------------------------------------------------------------------------------------- | ---------- | ----- | -| [Installing and Running Python](https://foundations.projectpythia.org/foundations/how-to-run-python.html) | Helpful | | - -- **Time to learn**: 20 minutes - ---- - -## Installing Python in the Terminal - -If you are running Python in the terminal, it is best to install Miniconda. You can do that by following the [instructions for your machine](https://docs.conda.io/en/latest/miniconda.html). - -[Learn more about Conda here](conda.md) - -Then create a Conda environment with Python installed by typing the following into your terminal: - -``` -$ conda create --name pythia_foundations_env python -``` - -You can test this by running `python` in the command line. - -## Running Python in the Terminal - -On Windows, open **Anaconda Prompt**. On a Mac or Linux machine, simply open **Terminal**. - -1. Activate your Conda environment: - - ``` - $ conda activate pythia_foundations_env - ``` - -2. Create a directory to store our work. Let's call it `pythia-foundations`. - - ``` - $ mkdir pythia-foundations - ``` - -3. Go into the directory: - - ``` - $ cd pythia-foundations - ``` - -4. Create a new Python file: - - ``` - $ touch mysci.py - ``` - -5. And now that you've set up our workspace, edit the `mysci.py` script using your favorite text editor (e.g., nano): - - ``` - $ nano mysci.py - ``` - -6. Change the script to include the classic first command: printing, "Hello, world!". - - ```python - print("Hello, world!") - ``` - -7. Save your file and exit the editor. How to do this is dependent on your chosen text editor. - - - In Vim, revert to command mode by pressing `esc`. Then, the command is `:wq`. - - In Nano it is {kbd}`Ctrl`\+{kbd}`O` to save and {kbd}`Ctrl`\+{kbd}`X` to exit (where you will be prompted if you want to save it, if modified). - -8. In the terminal, execute your script: - - ``` - $ python mysci.py - ``` - -**Congratulations!** You have just set up your first Python environment and run your first Python script in the terminal. - ---- - -## Summary - -Running Python in the terminal is a good option if you are familiar with Linux commands or scripting on a supercomputer. It requires the use of a text editor. - -### What's Next? - -- [How to Run Python in a Jupyter Session](jupyter.md) -- [Learn more about Conda here](conda.md) - -## Resources and References - -- [Linux commands](https://cheatography.com/davechild/cheat-sheets/linux-command-line/) diff --git a/_preview/419/_sources/foundations/why-python.md b/_preview/419/_sources/foundations/why-python.md deleted file mode 100644 index cd31cf975..000000000 --- a/_preview/419/_sources/foundations/why-python.md +++ /dev/null @@ -1,27 +0,0 @@ -# Why Python? - -You're already here because you want to learn to use Python for your data analysis and visualizations. - -**Perhaps the #1 reason to use Python is because it is so widely used in the scientific community!** - -Python can be compared to other high-level, interpreted, object-oriented languages, but is especially great because it is free and open source! - -Want to know what these terms mean for you and your work? Read on! - -## High level languages - -Other high level languages include MatLab, IDL, and NCL. The advantage of high level languages is that they provide built-in functions, data structures, and other utilities that are commonly used, which means it takes less code to get real work done. The disadvantage of high level languages is that they tend to obscure the low level aspects of the machine such as memory use, how many floating point operations are happening, and other information related to performance. C, C++, and Fortran are all examples of lower level languages. The "higher" the level of language, the more computing fundamentals are abstracted. - -## Interpreted languages - -Most of your work is probably already in interpreted languages if you've ever used IDL, NCL, or MatLab (interpreted languages are typically also high level). So you are already familiar with the advantages of this: you don't have to worry about compiling or machine compatibility (it is portable). And you are probably familiar with their deficiencies: sometimes they can be slower than compiled languages and potentially more memory intensive. - -## Object Oriented languages - -Objects are custom datatypes. For every custom datatype, you usually have a set of operations you might want to conduct. For example, if you have an object that is a list of numbers, you might want to apply a mathematical operation, such as sum, onto this list object in bulk. Not every function can be applied to every datatype; it wouldn't make sense to apply a logarithm to a string of letters or to capitalize a list of numbers. Data and the operations applied to them are grouped together into one object. - -## Open source - -Python as a language is open source, which means that there is a community of developers behind its codebase. Anyone can join the developer community and contribute to deciding the future of the language. When someone identifies gaps in Python's abilities, they can write up the code to fill these gaps. The open source nature of Python means that Python as a language is very adaptable to the shifting needs of the user community. This harkens back to the idea that the widespread use of Python within the scientific community is a benefit to you! The large Python user base within your field has established high level community Python packages that are available to you in your workflow. - -Python is a language designed for rapid prototyping and efficient programming. It is easy to write new code quickly with less typing. diff --git a/_preview/419/_sources/landing-page.md b/_preview/419/_sources/landing-page.md deleted file mode 100644 index b75d742b0..000000000 --- a/_preview/419/_sources/landing-page.md +++ /dev/null @@ -1,11 +0,0 @@ -# Pythia Foundations - -## A community learning resource for Python-based computing in the geosciences - -Pretty Earth - -This collection covers the foundational skills everyone needs to get started with scientific computing in the open-source Python ecosystem. - -Project Pythia logo _Brought to you by [Project Pythia](https://projectpythia.org), the education working group for [Pangeo](https://pangeo.io)_ Pangeo logo - -[![DOI](https://zenodo.org/badge/338145160.svg)](https://zenodo.org/badge/latestdoi/338145160) diff --git a/_preview/419/_sources/preamble/how-to-cite.md b/_preview/419/_sources/preamble/how-to-cite.md deleted file mode 100644 index a8fcf9f31..000000000 --- a/_preview/419/_sources/preamble/how-to-cite.md +++ /dev/null @@ -1,10 +0,0 @@ -# How to Cite This Book - -The material in Pythia Foundations is licensed for free and open consumption and reuse. All code is served under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0), while all non-code content is licensed under [Creative Commons BY 4.0 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/). Effectively, this means you are free to share and adapt this material so long as you give appropriate credit to the Project Pythia community. - -The source code for the book is [released on GitHub](https://github.com/ProjectPythia/pythia-foundations) and archived on Zenodo. This DOI will always resolve to the latest version of the book source: - - -If material in Pythia Foundations is useful in published work, you can cite a specific version of the book as: - -> Rose, B. E. J., Kent, J., Tyle, K., Clyne, J., Banihirwe, A., Camron, D., May, R., Grover, M., Ford, R. R., Paul, K., Morley, J., Eroglu, O., Kailyn, L., & Zacharias, A. (2023). Pythia Foundations (Version v2023.05.01) [https://doi.org/10.5281/zenodo.7884572](https://doi.org/10.5281/zenodo.7884572) diff --git a/_preview/419/_sources/preamble/how-to-use.md b/_preview/419/_sources/preamble/how-to-use.md deleted file mode 100644 index e4e887102..000000000 --- a/_preview/419/_sources/preamble/how-to-use.md +++ /dev/null @@ -1,133 +0,0 @@ -# How to Use This Book - -## Preamble - -Pythia Foundations is intended to educate the reader on the essentials -for using the Scientific Python Ecosystem (SPE): a collection of -open source Python packages that support analysis, manipulation, -and visualization of scientific data. While Project Pythia is focused -on the geoscience communities, the material contained in Pythia -Foundations applies broadly, and may be useful to scientists, or -aspiring scientists, working in any number of disciplines. Pythia -Foundations makes very few assumptions about the experience level -of the reader other than having a background in math or science, -and being comfortable using a computer, including the command line -terminal (i.e. the [Unix shell](https://en.wikipedia.org/wiki/Unix_shell)). -Prior programming experience is not required. - -Lastly, in addition to the Python language and a number of fundamental -scientific Python packages, Pythia Foundations covers two topics -that we believe are essential -to effectively using the SPE: GitHub, for sharing workflows and -managing code; and Jupyter Notebooks, the technology by which all -Pythia Foundations material is presented and which is quickly -becoming a standard format for scientific communication of computational -results. - -## Organization of Pythia Foundations - -Pythia Foundations is organized into two main sections as seen in -the sidebar on the left: Foundational Skills, and Core Scientific -Python Packages. The first, Foundational Skills, covers essential -material that all users of Project Pythia are expected to feel -comfortable with in order to make the most of the rest of the Project -Pythia content. The second, Core Scientific Python Packages, covers -what we believe are the most important and fundamental packages -in the Scientific Python Ecosystem. These packages serve as the -building blocks for many of the more geoscience focused components -of the Scientific Python Ecosystem. - -## Running Pythia Foundations examples - -All of the content in Pythia Foundations is authored in Markdown -and presented in the form of [Jupyter -Notebook](https://jupyterbook.org/intro.html) “chapters”. The power -of Jupyter Notebooks is that they can contain both static text and -executable code that you can interact with. When you navigate to a -book chapter such as [Matplotlib -Basics](../core/matplotlib/matplotlib-basics) -you will see static text, Python code, and the rendered output of -that code in the form of the many figures that appear. In the case -of Matplotlib Basics these figures are produced by Matplotlib itself. -Viewing content in this manner is not much different than reading -a hardbound textbook. To get the full benefit of Jupyter Books you -can run, and even modify, the example code in real time! This -interaction allows you to experiment with different parameters and -observe instantly how results change. - -There are two ways that you can execute the Pythia Foundations book -chapters. Both are described below. - -### Interacting with Jupyter Notebooks in the cloud via Binder - -The simplest way to interact with a Jupyter Notebook is through -[Binder](https://binder.projectpythia.org), which enables the execution of a -Jupyter Notebook in the cloud. The details of how this works are not -important for now. All you need to know is how to launch a Pythia -Foundations book chapter via Binder. Simply navigate your mouse to -the top right corner of the book chapter you are viewing and click -on the rocket ship icon, (see figure below), and be sure to select -“Launch Binder”. After a moment you should be presented with a -notebook that you can interact with. You’ll be able to execute code -and even change the example programs. At first the code cells -have no output, until you execute them by pressing -{kbd}`Shift`\+{kbd}`Enter`. Complete details on how to interact with -a live Jupyter notebook are described in [Getting Started with -Jupyter](https://foundations.projectpythia.org/foundations/getting-started-jupyter.html). - -### Interacting with Jupyter Books locally - -Sometimes it may make more sense to download a book chapter and run -it on your local laptop or PC. Perhaps you want to co-opt a book -for your own purposes, or load your own local data. Downloading an -individual chapter is trivial: click on the download icon, also -located in the top right corner of the book chapter you are viewing, -and select “.ipynb”. - -That was the easy part. Getting the notebook to execute locally may -take a little more work. The book was created to run in a particular -Python environment, managed with Conda. If you don't have a up-to-date -version of Conda on your machine, you'll want to install one. A brief -introduction to installing Conda is available [here](https://foundations.projectpythia.org/foundations/conda.html). - -Once you've installed Conda you will need to create and activate a Conda environment -that is compatible with Pythia Foundation's notebooks. This -can be done with two commands from the terminal, one to create the -environment and one to activate it: - -``` -conda env create --force -f https://raw.githubusercontent.com/ProjectPythia/pythia-foundations/main/environment.yml -conda activate pythia-book-dev -``` - -You should only need to create the environment once (run the first -command above). But if you download another notebook later, you will -need to activate _pythia-book-dev_ if -it is not currently active, for example if you open up a new -terminal window, or deactivate _pythia-book-dev_ explicitly with -the `conda` command. Again, more information on Conda can be -found [here](https://foundations.projectpythia.org/foundations/conda.html). - -Now that your _pythia-book-dev_ environment is activated, -change your working directory to the -location where you downloaded the notebook (.ipynb file) and start -the Jupyter Notebook server. For example if you downloaded the -notebook file to your home directory you would do: - -``` -cd ~ -jupyter lab -``` - -A local Jupyter Notebook server should open in your web browser. -Simply open the .ipynb file using the Notebook server’s file browser -and you are good to go. If you want to work with many Pythia Foundations -notebooks, you might want to “clone the site” -and download all of the notebooks. First click on the Pythia -Foundations GitHub icon (see figure below) and select repository. -Then follow the instructions in our Getting Started with GitHub -[guide](https://foundations.projectpythia.org/foundations/getting-started-github.html#) -for cloning a repository. The steps used above for configuring your -Conda environment should work for this method as well. - -![Annotated Pythia Foundations home page](../images/foundations_diagram.png) diff --git a/_preview/419/_sphinx_design_static/design-style.4045f2051d55cab465a707391d5b2007.min.css b/_preview/419/_sphinx_design_static/design-style.4045f2051d55cab465a707391d5b2007.min.css deleted file mode 100644 index 3225661c2..000000000 --- a/_preview/419/_sphinx_design_static/design-style.4045f2051d55cab465a707391d5b2007.min.css +++ /dev/null @@ -1 +0,0 @@ -.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative}details.sd-dropdown .sd-summary-title{font-weight:700;padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary{list-style:none;padding:1em}details.sd-dropdown summary .sd-octicon.no-title{vertical-align:middle}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown summary::-webkit-details-marker{display:none}details.sd-dropdown summary:focus{outline:none}details.sd-dropdown .sd-summary-icon{margin-right:.5em}details.sd-dropdown .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary:hover .sd-summary-up svg,details.sd-dropdown summary:hover .sd-summary-down svg{opacity:1;transform:scale(1.1)}details.sd-dropdown .sd-summary-up svg,details.sd-dropdown .sd-summary-down svg{display:block;opacity:.6}details.sd-dropdown .sd-summary-up,details.sd-dropdown .sd-summary-down{pointer-events:none;position:absolute;right:1em;top:1em}details.sd-dropdown[open]>.sd-summary-title .sd-summary-down{visibility:hidden}details.sd-dropdown:not([open])>.sd-summary-title .sd-summary-up{visibility:hidden}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #007bff;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0069d9;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem} diff --git a/_preview/419/_sphinx_design_static/design-tabs.js b/_preview/419/_sphinx_design_static/design-tabs.js deleted file mode 100644 index 36b38cf0d..000000000 --- a/_preview/419/_sphinx_design_static/design-tabs.js +++ /dev/null @@ -1,27 +0,0 @@ -var sd_labels_by_text = {}; - -function ready() { - const li = document.getElementsByClassName("sd-tab-label"); - for (const label of li) { - syncId = label.getAttribute("data-sync-id"); - if (syncId) { - label.onclick = onLabelClick; - if (!sd_labels_by_text[syncId]) { - sd_labels_by_text[syncId] = []; - } - sd_labels_by_text[syncId].push(label); - } - } -} - -function onLabelClick() { - // Activate other inputs with the same sync id. - syncId = this.getAttribute("data-sync-id"); - for (label of sd_labels_by_text[syncId]) { - if (label === this) continue; - label.previousElementSibling.checked = true; - } - window.localStorage.setItem("sphinx-design-last-tab", syncId); -} - -document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_preview/419/_static/__init__.py b/_preview/419/_static/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/_preview/419/_static/__pycache__/__init__.cpython-310.pyc b/_preview/419/_static/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 0a1ed641a..000000000 Binary files a/_preview/419/_static/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/_preview/419/_static/basic.css b/_preview/419/_static/basic.css deleted file mode 100644 index d54be8067..000000000 --- a/_preview/419/_static/basic.css +++ /dev/null @@ -1,906 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 270px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li p.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 450px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a.brackets:before, -span.brackets > a:before{ - content: "["; -} - -a.brackets:after, -span.brackets > a:after { - content: "]"; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, figure.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, figure.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, figure.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, figure.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar, -aside.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -aside.sidebar > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure, figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption, figcaption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number, -figcaption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text, -figcaption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - -/* -- object description styles --------------------------------------------- */ - -.sig { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; -} - -.sig-name, code.descname { - background-color: transparent; - font-weight: bold; -} - -.sig-name { - font-size: 1.1em; -} - -code.descname { - font-size: 1.2em; -} - -.sig-prename, code.descclassname { - background-color: transparent; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.sig-param.n { - font-style: italic; -} - -/* C++ specific styling */ - -.sig-inline.c-texpr, -.sig-inline.cpp-texpr { - font-family: unset; -} - -.sig.c .k, .sig.c .kt, -.sig.cpp .k, .sig.cpp .kt { - color: #0033B3; -} - -.sig.c .m, -.sig.cpp .m { - color: #1750EB; -} - -.sig.c .s, .sig.c .sc, -.sig.cpp .s, .sig.cpp .sc { - color: #067D17; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -dl.footnote > dt, -dl.citation > dt { - float: left; - margin-right: 0.5em; -} - -dl.footnote > dd, -dl.citation > dd { - margin-bottom: 0em; -} - -dl.footnote > dd:after, -dl.citation > dd:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dt:after { - content: ":"; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0 0.5em; - content: ":"; - display: inline-block; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; - white-space: nowrap; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; - -webkit-user-select: text; /* Safari fallback only */ - -webkit-user-select: none; /* Chrome/Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+ */ -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/_preview/419/_static/check-solid.svg b/_preview/419/_static/check-solid.svg deleted file mode 100644 index 92fad4b5c..000000000 --- a/_preview/419/_static/check-solid.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/_preview/419/_static/clipboard.min.js b/_preview/419/_static/clipboard.min.js deleted file mode 100644 index 54b3c4638..000000000 --- a/_preview/419/_static/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.8 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 - - - - diff --git a/_preview/419/_static/copybutton.css b/_preview/419/_static/copybutton.css deleted file mode 100644 index f1916ec7d..000000000 --- a/_preview/419/_static/copybutton.css +++ /dev/null @@ -1,94 +0,0 @@ -/* Copy buttons */ -button.copybtn { - position: absolute; - display: flex; - top: .3em; - right: .3em; - width: 1.7em; - height: 1.7em; - opacity: 0; - transition: opacity 0.3s, border .3s, background-color .3s; - user-select: none; - padding: 0; - border: none; - outline: none; - border-radius: 0.4em; - /* The colors that GitHub uses */ - border: #1b1f2426 1px solid; - background-color: #f6f8fa; - color: #57606a; -} - -button.copybtn.success { - border-color: #22863a; - color: #22863a; -} - -button.copybtn svg { - stroke: currentColor; - width: 1.5em; - height: 1.5em; - padding: 0.1em; -} - -div.highlight { - position: relative; -} - -/* Show the copybutton */ -.highlight:hover button.copybtn, button.copybtn.success { - opacity: 1; -} - -.highlight button.copybtn:hover { - background-color: rgb(235, 235, 235); -} - -.highlight button.copybtn:active { - background-color: rgb(187, 187, 187); -} - -/** - * A minimal CSS-only tooltip copied from: - * https://codepen.io/mildrenben/pen/rVBrpK - * - * To use, write HTML like the following: - * - *

Short

- */ - .o-tooltip--left { - position: relative; - } - - .o-tooltip--left:after { - opacity: 0; - visibility: hidden; - position: absolute; - content: attr(data-tooltip); - padding: .2em; - font-size: .8em; - left: -.2em; - background: grey; - color: white; - white-space: nowrap; - z-index: 2; - border-radius: 2px; - transform: translateX(-102%) translateY(0); - transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); -} - -.o-tooltip--left:hover:after { - display: block; - opacity: 1; - visibility: visible; - transform: translateX(-100%) translateY(0); - transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); - transition-delay: .5s; -} - -/* By default the copy button shouldn't show up when printing a page */ -@media print { - button.copybtn { - display: none; - } -} diff --git a/_preview/419/_static/copybutton.js b/_preview/419/_static/copybutton.js deleted file mode 100644 index 2ea7ff3e2..000000000 --- a/_preview/419/_static/copybutton.js +++ /dev/null @@ -1,248 +0,0 @@ -// Localization support -const messages = { - 'en': { - 'copy': 'Copy', - 'copy_to_clipboard': 'Copy to clipboard', - 'copy_success': 'Copied!', - 'copy_failure': 'Failed to copy', - }, - 'es' : { - 'copy': 'Copiar', - 'copy_to_clipboard': 'Copiar al portapapeles', - 'copy_success': '¡Copiado!', - 'copy_failure': 'Error al copiar', - }, - 'de' : { - 'copy': 'Kopieren', - 'copy_to_clipboard': 'In die Zwischenablage kopieren', - 'copy_success': 'Kopiert!', - 'copy_failure': 'Fehler beim Kopieren', - }, - 'fr' : { - 'copy': 'Copier', - 'copy_to_clipboard': 'Copier dans le presse-papier', - 'copy_success': 'Copié !', - 'copy_failure': 'Échec de la copie', - }, - 'ru': { - 'copy': 'Скопировать', - 'copy_to_clipboard': 'Скопировать в буфер', - 'copy_success': 'Скопировано!', - 'copy_failure': 'Не удалось скопировать', - }, - 'zh-CN': { - 'copy': '复制', - 'copy_to_clipboard': '复制到剪贴板', - 'copy_success': '复制成功!', - 'copy_failure': '复制失败', - }, - 'it' : { - 'copy': 'Copiare', - 'copy_to_clipboard': 'Copiato negli appunti', - 'copy_success': 'Copiato!', - 'copy_failure': 'Errore durante la copia', - } -} - -let locale = 'en' -if( document.documentElement.lang !== undefined - && messages[document.documentElement.lang] !== undefined ) { - locale = document.documentElement.lang -} - -let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; -if (doc_url_root == '#') { - doc_url_root = ''; -} - -/** - * SVG files for our copy buttons - */ -let iconCheck = ` - ${messages[locale]['copy_success']} - - -` - -// If the user specified their own SVG use that, otherwise use the default -let iconCopy = ``; -if (!iconCopy) { - iconCopy = ` - ${messages[locale]['copy_to_clipboard']} - - - -` -} - -/** - * Set up copy/paste for code blocks - */ - -const runWhenDOMLoaded = cb => { - if (document.readyState != 'loading') { - cb() - } else if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', cb) - } else { - document.attachEvent('onreadystatechange', function() { - if (document.readyState == 'complete') cb() - }) - } -} - -const codeCellId = index => `codecell${index}` - -// Clears selected text since ClipboardJS will select the text when copying -const clearSelection = () => { - if (window.getSelection) { - window.getSelection().removeAllRanges() - } else if (document.selection) { - document.selection.empty() - } -} - -// Changes tooltip text for a moment, then changes it back -// We want the timeout of our `success` class to be a bit shorter than the -// tooltip and icon change, so that we can hide the icon before changing back. -var timeoutIcon = 2000; -var timeoutSuccessClass = 1500; - -const temporarilyChangeTooltip = (el, oldText, newText) => { - el.setAttribute('data-tooltip', newText) - el.classList.add('success') - // Remove success a little bit sooner than we change the tooltip - // So that we can use CSS to hide the copybutton first - setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) - setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) -} - -// Changes the copy button icon for two seconds, then changes it back -const temporarilyChangeIcon = (el) => { - el.innerHTML = iconCheck; - setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) -} - -const addCopyButtonToCodeCells = () => { - // If ClipboardJS hasn't loaded, wait a bit and try again. This - // happens because we load ClipboardJS asynchronously. - if (window.ClipboardJS === undefined) { - setTimeout(addCopyButtonToCodeCells, 250) - return - } - - // Add copybuttons to all of our code cells - const COPYBUTTON_SELECTOR = 'div.highlight pre'; - const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) - codeCells.forEach((codeCell, index) => { - const id = codeCellId(index) - codeCell.setAttribute('id', id) - - const clipboardButton = id => - `` - codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) - }) - -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Removes excluded text from a Node. - * - * @param {Node} target Node to filter. - * @param {string} exclude CSS selector of nodes to exclude. - * @returns {DOMString} Text from `target` with text removed. - */ -function filterText(target, exclude) { - const clone = target.cloneNode(true); // clone as to not modify the live DOM - if (exclude) { - // remove excluded nodes - clone.querySelectorAll(exclude).forEach(node => node.remove()); - } - return clone.innerText; -} - -// Callback when a copy button is clicked. Will be passed the node that was clicked -// should then grab the text and replace pieces of text that shouldn't be used in output -function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { - var regexp; - var match; - - // Do we check for line continuation characters and "HERE-documents"? - var useLineCont = !!lineContinuationChar - var useHereDoc = !!hereDocDelim - - // create regexp to capture prompt and remaining line - if (isRegexp) { - regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') - } else { - regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') - } - - const outputLines = []; - var promptFound = false; - var gotLineCont = false; - var gotHereDoc = false; - const lineGotPrompt = []; - for (const line of textContent.split('\n')) { - match = line.match(regexp) - if (match || gotLineCont || gotHereDoc) { - promptFound = regexp.test(line) - lineGotPrompt.push(promptFound) - if (removePrompts && promptFound) { - outputLines.push(match[2]) - } else { - outputLines.push(line) - } - gotLineCont = line.endsWith(lineContinuationChar) & useLineCont - if (line.includes(hereDocDelim) & useHereDoc) - gotHereDoc = !gotHereDoc - } else if (!onlyCopyPromptLines) { - outputLines.push(line) - } else if (copyEmptyLines && line.trim() === '') { - outputLines.push(line) - } - } - - // If no lines with the prompt were found then just use original lines - if (lineGotPrompt.some(v => v === true)) { - textContent = outputLines.join('\n'); - } - - // Remove a trailing newline to avoid auto-running when pasting - if (textContent.endsWith("\n")) { - textContent = textContent.slice(0, -1) - } - return textContent -} - - -var copyTargetText = (trigger) => { - var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); - - // get filtered text - let exclude = '.linenos'; - - let text = filterText(target, exclude); - return formatCopyText(text, '', false, true, true, true, '', '') -} - - // Initialize with a callback so we can modify the text before copy - const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) - - // Update UI with error/success messages - clipboard.on('success', event => { - clearSelection() - temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) - temporarilyChangeIcon(event.trigger) - }) - - clipboard.on('error', event => { - temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) - }) -} - -runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_preview/419/_static/copybutton_funcs.js b/_preview/419/_static/copybutton_funcs.js deleted file mode 100644 index dbe1aaad7..000000000 --- a/_preview/419/_static/copybutton_funcs.js +++ /dev/null @@ -1,73 +0,0 @@ -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Removes excluded text from a Node. - * - * @param {Node} target Node to filter. - * @param {string} exclude CSS selector of nodes to exclude. - * @returns {DOMString} Text from `target` with text removed. - */ -export function filterText(target, exclude) { - const clone = target.cloneNode(true); // clone as to not modify the live DOM - if (exclude) { - // remove excluded nodes - clone.querySelectorAll(exclude).forEach(node => node.remove()); - } - return clone.innerText; -} - -// Callback when a copy button is clicked. Will be passed the node that was clicked -// should then grab the text and replace pieces of text that shouldn't be used in output -export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { - var regexp; - var match; - - // Do we check for line continuation characters and "HERE-documents"? - var useLineCont = !!lineContinuationChar - var useHereDoc = !!hereDocDelim - - // create regexp to capture prompt and remaining line - if (isRegexp) { - regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') - } else { - regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') - } - - const outputLines = []; - var promptFound = false; - var gotLineCont = false; - var gotHereDoc = false; - const lineGotPrompt = []; - for (const line of textContent.split('\n')) { - match = line.match(regexp) - if (match || gotLineCont || gotHereDoc) { - promptFound = regexp.test(line) - lineGotPrompt.push(promptFound) - if (removePrompts && promptFound) { - outputLines.push(match[2]) - } else { - outputLines.push(line) - } - gotLineCont = line.endsWith(lineContinuationChar) & useLineCont - if (line.includes(hereDocDelim) & useHereDoc) - gotHereDoc = !gotHereDoc - } else if (!onlyCopyPromptLines) { - outputLines.push(line) - } else if (copyEmptyLines && line.trim() === '') { - outputLines.push(line) - } - } - - // If no lines with the prompt were found then just use original lines - if (lineGotPrompt.some(v => v === true)) { - textContent = outputLines.join('\n'); - } - - // Remove a trailing newline to avoid auto-running when pasting - if (textContent.endsWith("\n")) { - textContent = textContent.slice(0, -1) - } - return textContent -} diff --git a/_preview/419/_static/css/blank.css b/_preview/419/_static/css/blank.css deleted file mode 100644 index 8a686ec75..000000000 --- a/_preview/419/_static/css/blank.css +++ /dev/null @@ -1,2 +0,0 @@ -/* This file is intentionally left blank to override the stylesheet of the -parent theme via theme.conf. The parent style we import directly in theme.css */ \ No newline at end of file diff --git a/_preview/419/_static/css/index.ff1ffe594081f20da1ef19478df9384b.css b/_preview/419/_static/css/index.ff1ffe594081f20da1ef19478df9384b.css deleted file mode 100644 index 9b1c5d792..000000000 --- a/_preview/419/_static/css/index.ff1ffe594081f20da1ef19478df9384b.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Bootstrap v4.5.0 (https://getbootstrap.com/) - * Copyright 2011-2020 The Bootstrap Authors - * Copyright 2011-2020 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:540px;--breakpoint-md:720px;--breakpoint-lg:960px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:1rem;line-height:1.5;color:#212529;text-align:left}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;background-color:transparent}a:hover{color:#0056b3}a:not([href]),a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem}.display-1,.display-2{font-weight:300;line-height:1.2}.display-2{font-size:5.5rem}.display-3{font-size:4.5rem}.display-3,.display-4{font-weight:300;line-height:1.2}.display-4{font-size:3.5rem}hr{margin-top:1rem;margin-bottom:1rem;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer:before{content:"\2014\00A0"}.img-fluid,.img-thumbnail{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:540px){.container{max-width:540px}}@media (min-width:720px){.container{max-width:720px}}@media (min-width:960px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1400px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:540px){.container,.container-sm{max-width:540px}}@media (min-width:720px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:960px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1400px}}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col-auto,.col-lg,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-auto,.col-md,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md-auto,.col-sm,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:540px){.col-sm{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:720px){.col-md{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:960px){.col-lg{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th,.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:hsla(0,0%,100%,.075)}@media (max-width:539.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:719.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:959.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:540px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{display:flex;align-items:center;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success.focus,.btn-success:focus,.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info.focus,.btn-info:focus,.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light.focus,.btn-light:focus,.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:540px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:720px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:960px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";display:none}.dropleft .dropdown-toggle:before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio],.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label:before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label:before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label:before{pointer-events:none;background-color:#fff;border:1px solid #adb5bd}.custom-control-label:after,.custom-control-label:before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:""}.custom-control-label:after{background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label:after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{position:relative;width:100%;height:calc(1.5em + .75rem + 2px)}.custom-file-input{z-index:2;margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{left:0;z-index:1;height:calc(1.5em + .75rem + 2px);font-weight:400;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label,.custom-file-label:after{position:absolute;top:0;right:0;padding:.375rem .75rem;line-height:1.5;color:#495057}.custom-file-label:after{bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;padding:.5rem 1rem}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat 50%;background-size:100% 100%}@media (max-width:539.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:540px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:719.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:720px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:959.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:960px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0,0,0,0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:hsla(0,0%,100%,.5);border-color:hsla(0,0%,100%,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255,255,255,0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-bottom:-.75rem;border-bottom:0}.card-header-pills,.card-header-tabs{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:540px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:540px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:540px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb,.breadcrumb-item{display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:540px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{height:1rem;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:540px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:720px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:960px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{display:block;height:calc(100vh - 1rem);height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:540px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:min-content}.modal-sm{max-width:300px}}@media (min-width:960px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow:before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{top:0;left:0;z-index:1060;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover,.popover .arrow{position:absolute;display:block}.popover .arrow{width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow:after,.popover .arrow:before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{top:0;border-width:0 .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{top:1px;border-width:0 .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner:after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid;border-right:.25em solid transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:540px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:720px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:960px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive:before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9:before{padding-top:42.85714%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:540px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:720px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:960px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:540px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:720px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:960px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{user-select:all!important}.user-select-auto{user-select:auto!important}.user-select-none{user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{position:fixed;right:0;left:0;z-index:1030}.fixed-bottom{bottom:0}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:540px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:720px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:960px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:transparent}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:540px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:720px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:960px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:960px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}}html{font-size:var(--pst-font-size-base);scroll-padding-top:calc(var(--pst-header-height) + 12px)}body{padding-top:calc(var(--pst-header-height) + 20px);background-color:#fff;font-family:var(--pst-font-family-base);font-weight:400;line-height:1.65;color:rgba(var(--pst-color-text-base),1)}p{margin-bottom:1.15rem;font-size:1em;color:rgba(var(--pst-color-paragraph),1)}p.rubric{border-bottom:1px solid #c9c9c9}a{color:rgba(var(--pst-color-link),1);text-decoration:none}a:hover{color:rgba(var(--pst-color-link-hover),1);text-decoration:underline}a.headerlink{color:rgba(var(--pst-color-headerlink),1);font-size:.8em;padding:0 4px;text-decoration:none}a.headerlink:hover{background-color:rgba(var(--pst-color-headerlink),1);color:rgba(var(--pst-color-headerlink-hover),1)}.heading-style,h1,h2,h3,h4,h5,h6{margin:2.75rem 0 1.05rem;font-family:var(--pst-font-family-heading);font-weight:400;line-height:1.15}h1{margin-top:0;font-size:var(--pst-font-size-h1);color:rgba(var(--pst-color-h1),1)}h2{font-size:var(--pst-font-size-h2);color:rgba(var(--pst-color-h2),1)}h3{font-size:var(--pst-font-size-h3);color:rgba(var(--pst-color-h3),1)}h4{font-size:var(--pst-font-size-h4);color:rgba(var(--pst-color-h4),1)}h5{font-size:var(--pst-font-size-h5);color:rgba(var(--pst-color-h5),1)}h6{font-size:var(--pst-font-size-h6);color:rgba(var(--pst-color-h6),1)}.text_small,small{font-size:var(--pst-font-size-milli)}hr{border:0;border-top:1px solid #e5e5e5}code,kbd,pre,samp{font-family:var(--pst-font-family-monospace)}code{color:rgba(var(--pst-color-inline-code),1)}pre{margin:1.5em 0;padding:10px;background-color:rgba(var(--pst-color-preformatted-background),1);color:rgba(var(--pst-color-preformatted-text),1);line-height:1.2em;border:1px solid #c9c9c9;border-radius:.2rem;box-shadow:1px 1px 1px #d8d8d8}dd{margin-top:3px;margin-bottom:10px;margin-left:30px}.navbar{position:fixed;min-height:var(--pst-header-height);width:100%;padding:0}.navbar .container-xl{height:100%}@media (min-width:960px){.navbar #navbar-end>.navbar-end-item{display:inline-block}}.navbar-brand{position:relative;height:var(--pst-header-height);width:auto;padding:.5rem 0}.navbar-brand img{max-width:100%;height:100%;width:auto}.navbar-light{background:#fff!important;box-shadow:0 .125rem .25rem 0 rgba(0,0,0,.11)}.navbar-light .navbar-nav li a.nav-link{padding:0 .5rem;color:rgba(var(--pst-color-navbar-link),1)}.navbar-light .navbar-nav li a.nav-link:hover{color:rgba(var(--pst-color-navbar-link-hover),1)}.navbar-light .navbar-nav>.active>.nav-link{font-weight:600;color:rgba(var(--pst-color-navbar-link-active),1)}.navbar-header a{padding:0 15px}.admonition,div.admonition{margin:1.5625em auto;padding:0 .6rem .8rem;overflow:hidden;page-break-inside:avoid;border-left:.2rem solid;border-left-color:rgba(var(--pst-color-admonition-default),1);border-bottom-color:rgba(var(--pst-color-admonition-default),1);border-right-color:rgba(var(--pst-color-admonition-default),1);border-top-color:rgba(var(--pst-color-admonition-default),1);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);transition:color .25s,background-color .25s,border-color .25s}.admonition :last-child,div.admonition :last-child{margin-bottom:0}.admonition p.admonition-title~*,div.admonition p.admonition-title~*{padding:0 1.4rem}.admonition>ol,.admonition>ul,div.admonition>ol,div.admonition>ul{margin-left:1em}.admonition>.admonition-title,div.admonition>.admonition-title{position:relative;margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(var(--pst-color-admonition-default),.1)}.admonition>.admonition-title:before,div.admonition>.admonition-title:before{position:absolute;left:.6rem;width:1rem;height:1rem;color:rgba(var(--pst-color-admonition-default),1);font-family:Font Awesome\ 5 Free;font-weight:900;content:var(--pst-icon-admonition-default)}.admonition>.admonition-title+*,div.admonition>.admonition-title+*{margin-top:.4em}.admonition.attention,div.admonition.attention{border-color:rgba(var(--pst-color-admonition-attention),1)}.admonition.attention>.admonition-title,div.admonition.attention>.admonition-title{background-color:rgba(var(--pst-color-admonition-attention),.1)}.admonition.attention>.admonition-title:before,div.admonition.attention>.admonition-title:before{color:rgba(var(--pst-color-admonition-attention),1);content:var(--pst-icon-admonition-attention)}.admonition.caution,div.admonition.caution{border-color:rgba(var(--pst-color-admonition-caution),1)}.admonition.caution>.admonition-title,div.admonition.caution>.admonition-title{background-color:rgba(var(--pst-color-admonition-caution),.1)}.admonition.caution>.admonition-title:before,div.admonition.caution>.admonition-title:before{color:rgba(var(--pst-color-admonition-caution),1);content:var(--pst-icon-admonition-caution)}.admonition.warning,div.admonition.warning{border-color:rgba(var(--pst-color-admonition-warning),1)}.admonition.warning>.admonition-title,div.admonition.warning>.admonition-title{background-color:rgba(var(--pst-color-admonition-warning),.1)}.admonition.warning>.admonition-title:before,div.admonition.warning>.admonition-title:before{color:rgba(var(--pst-color-admonition-warning),1);content:var(--pst-icon-admonition-warning)}.admonition.danger,div.admonition.danger{border-color:rgba(var(--pst-color-admonition-danger),1)}.admonition.danger>.admonition-title,div.admonition.danger>.admonition-title{background-color:rgba(var(--pst-color-admonition-danger),.1)}.admonition.danger>.admonition-title:before,div.admonition.danger>.admonition-title:before{color:rgba(var(--pst-color-admonition-danger),1);content:var(--pst-icon-admonition-danger)}.admonition.error,div.admonition.error{border-color:rgba(var(--pst-color-admonition-error),1)}.admonition.error>.admonition-title,div.admonition.error>.admonition-title{background-color:rgba(var(--pst-color-admonition-error),.1)}.admonition.error>.admonition-title:before,div.admonition.error>.admonition-title:before{color:rgba(var(--pst-color-admonition-error),1);content:var(--pst-icon-admonition-error)}.admonition.hint,div.admonition.hint{border-color:rgba(var(--pst-color-admonition-hint),1)}.admonition.hint>.admonition-title,div.admonition.hint>.admonition-title{background-color:rgba(var(--pst-color-admonition-hint),.1)}.admonition.hint>.admonition-title:before,div.admonition.hint>.admonition-title:before{color:rgba(var(--pst-color-admonition-hint),1);content:var(--pst-icon-admonition-hint)}.admonition.tip,div.admonition.tip{border-color:rgba(var(--pst-color-admonition-tip),1)}.admonition.tip>.admonition-title,div.admonition.tip>.admonition-title{background-color:rgba(var(--pst-color-admonition-tip),.1)}.admonition.tip>.admonition-title:before,div.admonition.tip>.admonition-title:before{color:rgba(var(--pst-color-admonition-tip),1);content:var(--pst-icon-admonition-tip)}.admonition.important,div.admonition.important{border-color:rgba(var(--pst-color-admonition-important),1)}.admonition.important>.admonition-title,div.admonition.important>.admonition-title{background-color:rgba(var(--pst-color-admonition-important),.1)}.admonition.important>.admonition-title:before,div.admonition.important>.admonition-title:before{color:rgba(var(--pst-color-admonition-important),1);content:var(--pst-icon-admonition-important)}.admonition.note,div.admonition.note{border-color:rgba(var(--pst-color-admonition-note),1)}.admonition.note>.admonition-title,div.admonition.note>.admonition-title{background-color:rgba(var(--pst-color-admonition-note),.1)}.admonition.note>.admonition-title:before,div.admonition.note>.admonition-title:before{color:rgba(var(--pst-color-admonition-note),1);content:var(--pst-icon-admonition-note)}table.field-list{border-collapse:separate;border-spacing:10px;margin-left:1px}table.field-list th.field-name{padding:1px 8px 1px 5px;white-space:nowrap;background-color:#eee}table.field-list td.field-body p{font-style:italic}table.field-list td.field-body p>strong{font-style:normal}table.field-list td.field-body blockquote{border-left:none;margin:0 0 .3em;padding-left:30px}.table.autosummary td:first-child{white-space:nowrap}.sig{font-family:var(--pst-font-family-monospace)}.sig-inline.c-texpr,.sig-inline.cpp-texpr{font-family:unset}.sig.c .k,.sig.c .kt,.sig.c .m,.sig.c .s,.sig.c .sc,.sig.cpp .k,.sig.cpp .kt,.sig.cpp .m,.sig.cpp .s,.sig.cpp .sc{color:rgba(var(--pst-color-text-base),1)}.sig-name{color:rgba(var(--pst-color-inline-code),1)}blockquote{padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}dt.label>span.brackets:not(:only-child):before{content:"["}dt.label>span.brackets:not(:only-child):after{content:"]"}a.footnote-reference{vertical-align:super;font-size:small}div.deprecated{margin-bottom:10px;margin-top:10px;padding:7px;background-color:#f3e5e5;border:1px solid #eed3d7;border-radius:.5rem}div.deprecated p{color:#b94a48;display:inline}.topic{background-color:#eee}.seealso dd{margin-top:0;margin-bottom:0}.viewcode-back{font-family:var(--pst-font-family-base)}.viewcode-block:target{background-color:#f4debf;border-top:1px solid #ac9;border-bottom:1px solid #ac9}span.guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}footer{width:100%;border-top:1px solid #ccc;padding:10px}footer .footer-item p{margin-bottom:0}.bd-search{position:relative;padding:1rem 15px;margin-right:-15px;margin-left:-15px}.bd-search .icon{position:absolute;color:#a4a6a7;left:25px;top:25px}.bd-search input{border-radius:0;border:0;border-bottom:1px solid #e5e5e5;padding-left:35px}.bd-toc{-ms-flex-order:2;order:2;height:calc(100vh - 2rem);overflow-y:auto}@supports (position:-webkit-sticky) or (position:sticky){.bd-toc{position:-webkit-sticky;position:sticky;top:calc(var(--pst-header-height) + 20px);height:calc(100vh - 5rem);overflow-y:auto}}.bd-toc .onthispage{color:#a4a6a7}.section-nav{padding-left:0;border-left:1px solid #eee;border-bottom:none}.section-nav ul{padding-left:1rem}.toc-entry,.toc-entry a{display:block}.toc-entry a{padding:.125rem 1.5rem;color:rgba(var(--pst-color-toc-link),1)}@media (min-width:1200px){.toc-entry a{padding-right:0}}.toc-entry a:hover{color:rgba(var(--pst-color-toc-link-hover),1);text-decoration:none}.bd-sidebar{padding-top:1em}@media (min-width:720px){.bd-sidebar{border-right:1px solid rgba(0,0,0,.1)}@supports (position:-webkit-sticky) or (position:sticky){.bd-sidebar{position:-webkit-sticky;position:sticky;top:calc(var(--pst-header-height) + 20px);z-index:1000;height:calc(100vh - var(--pst-header-height) - 20px)}}}.bd-sidebar.no-sidebar{border-right:0}.bd-links{padding-top:1rem;padding-bottom:1rem;margin-right:-15px;margin-left:-15px}@media (min-width:720px){.bd-links{display:block}@supports (position:-webkit-sticky) or (position:sticky){.bd-links{max-height:calc(100vh - 11rem);overflow-y:auto}}}.bd-sidenav{display:none}.bd-content{padding-top:20px}.bd-content .section{max-width:100%}.bd-content .section table{display:block;overflow:auto}.bd-toc-link{display:block;padding:.25rem 1.5rem;font-weight:600;color:rgba(0,0,0,.65)}.bd-toc-link:hover{color:rgba(0,0,0,.85);text-decoration:none}.bd-toc-item.active{margin-bottom:1rem}.bd-toc-item.active:not(:first-child){margin-top:1rem}.bd-toc-item.active>.bd-toc-link{color:rgba(0,0,0,.85)}.bd-toc-item.active>.bd-toc-link:hover{background-color:transparent}.bd-toc-item.active>.bd-sidenav{display:block}nav.bd-links p.caption{font-size:var(--pst-sidebar-caption-font-size);text-transform:uppercase;font-weight:700;position:relative;margin-top:1.25em;margin-bottom:.5em;padding:0 1.5rem;color:rgba(var(--pst-color-sidebar-caption),1)}nav.bd-links p.caption:first-child{margin-top:0}.bd-sidebar .nav{font-size:var(--pst-sidebar-font-size)}.bd-sidebar .nav ul{list-style:none;padding:0 0 0 1.5rem}.bd-sidebar .nav li>a{display:block;padding:.25rem 1.5rem;color:rgba(var(--pst-color-sidebar-link),1)}.bd-sidebar .nav li>a:hover{color:rgba(var(--pst-color-sidebar-link-hover),1);text-decoration:none;background-color:transparent}.bd-sidebar .nav li>a.reference.external:after{font-family:Font Awesome\ 5 Free;font-weight:900;content:"\f35d";font-size:.75em;margin-left:.3em}.bd-sidebar .nav .active:hover>a,.bd-sidebar .nav .active>a{font-weight:600;color:rgba(var(--pst-color-sidebar-link-active),1)}.toc-h2{font-size:.85rem}.toc-h3{font-size:.75rem}.toc-h4{font-size:.65rem}.toc-entry>.nav-link.active{font-weight:600;color:#130654;color:rgba(var(--pst-color-toc-link-active),1);background-color:transparent;border-left:2px solid rgba(var(--pst-color-toc-link-active),1)}.nav-link:hover{border-style:none}#navbar-main-elements li.nav-item i{font-size:.7rem;padding-left:2px;vertical-align:middle}.bd-toc .nav .nav{display:none}.bd-toc .nav .nav.visible,.bd-toc .nav>.active>ul{display:block}.prev-next-area{margin:20px 0}.prev-next-area p{margin:0 .3em;line-height:1.3em}.prev-next-area i{font-size:1.2em}.prev-next-area a{display:flex;align-items:center;border:none;padding:10px;max-width:45%;overflow-x:hidden;color:rgba(0,0,0,.65);text-decoration:none}.prev-next-area a p.prev-next-title{color:rgba(var(--pst-color-link),1);font-weight:600;font-size:1.1em}.prev-next-area a:hover p.prev-next-title{text-decoration:underline}.prev-next-area a .prev-next-info{flex-direction:column;margin:0 .5em}.prev-next-area a .prev-next-info .prev-next-subtitle{text-transform:capitalize}.prev-next-area a.left-prev{float:left}.prev-next-area a.right-next{float:right}.prev-next-area a.right-next div.prev-next-info{text-align:right}.alert{padding-bottom:0}.alert-info a{color:#e83e8c}#navbar-icon-links i.fa,#navbar-icon-links i.fab,#navbar-icon-links i.far,#navbar-icon-links i.fas{vertical-align:middle;font-style:normal;font-size:1.5rem;line-height:1.25}#navbar-icon-links i.fa-github-square:before{color:#333}#navbar-icon-links i.fa-twitter-square:before{color:#55acee}#navbar-icon-links i.fa-gitlab:before{color:#548}#navbar-icon-links i.fa-bitbucket:before{color:#0052cc}.tocsection{border-left:1px solid #eee;padding:.3rem 1.5rem}.tocsection i{padding-right:.5rem}.editthispage{padding-top:2rem}.editthispage a{color:var(--pst-color-sidebar-link-active)}.xr-wrap[hidden]{display:block!important}.toctree-checkbox{position:absolute;display:none}.toctree-checkbox~ul{display:none}.toctree-checkbox~label i{transform:rotate(0deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label i{transform:rotate(180deg)}.bd-sidebar li{position:relative}.bd-sidebar label{position:absolute;top:0;right:0;height:30px;width:30px;cursor:pointer;display:flex;justify-content:center;align-items:center}.bd-sidebar label:hover{background:rgba(var(--pst-color-sidebar-expander-background-hover),1)}.bd-sidebar label i{display:inline-block;font-size:.75rem;text-align:center}.bd-sidebar label i:hover{color:rgba(var(--pst-color-sidebar-link-hover),1)}.bd-sidebar li.has-children>.reference{padding-right:30px}div.doctest>div.highlight span.gp,span.linenos,table.highlighttable td.linenos{user-select:none;-webkit-user-select:text;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.docutils.container{padding-left:unset;padding-right:unset} \ No newline at end of file diff --git a/_preview/419/_static/css/theme.css b/_preview/419/_static/css/theme.css deleted file mode 100644 index 2e03fe372..000000000 --- a/_preview/419/_static/css/theme.css +++ /dev/null @@ -1,120 +0,0 @@ -/* Provided by the Sphinx base theme template at build time */ -@import "../basic.css"; - -:root { - /***************************************************************************** - * Theme config - **/ - --pst-header-height: 60px; - - /***************************************************************************** - * Font size - **/ - --pst-font-size-base: 15px; /* base font size - applied at body / html level */ - - /* heading font sizes */ - --pst-font-size-h1: 36px; - --pst-font-size-h2: 32px; - --pst-font-size-h3: 26px; - --pst-font-size-h4: 21px; - --pst-font-size-h5: 18px; - --pst-font-size-h6: 16px; - - /* smaller then heading font sizes*/ - --pst-font-size-milli: 12px; - - --pst-sidebar-font-size: .9em; - --pst-sidebar-caption-font-size: .9em; - - /***************************************************************************** - * Font family - **/ - /* These are adapted from https://systemfontstack.com/ */ - --pst-font-family-base-system: -apple-system, BlinkMacSystemFont, Segoe UI, "Helvetica Neue", - Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; - --pst-font-family-monospace-system: "SFMono-Regular", Menlo, Consolas, Monaco, - Liberation Mono, Lucida Console, monospace; - - --pst-font-family-base: var(--pst-font-family-base-system); - --pst-font-family-heading: var(--pst-font-family-base); - --pst-font-family-monospace: var(--pst-font-family-monospace-system); - - /***************************************************************************** - * Color - * - * Colors are defined in rgb string way, "red, green, blue" - **/ - --pst-color-primary: 19, 6, 84; - --pst-color-success: 40, 167, 69; - --pst-color-info: 0, 123, 255; /*23, 162, 184;*/ - --pst-color-warning: 255, 193, 7; - --pst-color-danger: 220, 53, 69; - --pst-color-text-base: 51, 51, 51; - - --pst-color-h1: var(--pst-color-primary); - --pst-color-h2: var(--pst-color-primary); - --pst-color-h3: var(--pst-color-text-base); - --pst-color-h4: var(--pst-color-text-base); - --pst-color-h5: var(--pst-color-text-base); - --pst-color-h6: var(--pst-color-text-base); - --pst-color-paragraph: var(--pst-color-text-base); - --pst-color-link: 0, 91, 129; - --pst-color-link-hover: 227, 46, 0; - --pst-color-headerlink: 198, 15, 15; - --pst-color-headerlink-hover: 255, 255, 255; - --pst-color-preformatted-text: 34, 34, 34; - --pst-color-preformatted-background: 250, 250, 250; - --pst-color-inline-code: 232, 62, 140; - - --pst-color-active-navigation: 19, 6, 84; - --pst-color-navbar-link: 77, 77, 77; - --pst-color-navbar-link-hover: var(--pst-color-active-navigation); - --pst-color-navbar-link-active: var(--pst-color-active-navigation); - --pst-color-sidebar-link: 77, 77, 77; - --pst-color-sidebar-link-hover: var(--pst-color-active-navigation); - --pst-color-sidebar-link-active: var(--pst-color-active-navigation); - --pst-color-sidebar-expander-background-hover: 244, 244, 244; - --pst-color-sidebar-caption: 77, 77, 77; - --pst-color-toc-link: 119, 117, 122; - --pst-color-toc-link-hover: var(--pst-color-active-navigation); - --pst-color-toc-link-active: var(--pst-color-active-navigation); - - /***************************************************************************** - * Icon - **/ - - /* font awesome icons*/ - --pst-icon-check-circle: '\f058'; - --pst-icon-info-circle: '\f05a'; - --pst-icon-exclamation-triangle: '\f071'; - --pst-icon-exclamation-circle: '\f06a'; - --pst-icon-times-circle: '\f057'; - --pst-icon-lightbulb: '\f0eb'; - - /***************************************************************************** - * Admonitions - **/ - - --pst-color-admonition-default: var(--pst-color-info); - --pst-color-admonition-note: var(--pst-color-info); - --pst-color-admonition-attention: var(--pst-color-warning); - --pst-color-admonition-caution: var(--pst-color-warning); - --pst-color-admonition-warning: var(--pst-color-warning); - --pst-color-admonition-danger: var(--pst-color-danger); - --pst-color-admonition-error: var(--pst-color-danger); - --pst-color-admonition-hint: var(--pst-color-success); - --pst-color-admonition-tip: var(--pst-color-success); - --pst-color-admonition-important: var(--pst-color-success); - - --pst-icon-admonition-default: var(--pst-icon-info-circle); - --pst-icon-admonition-note: var(--pst-icon-info-circle); - --pst-icon-admonition-attention: var(--pst-icon-exclamation-circle); - --pst-icon-admonition-caution: var(--pst-icon-exclamation-triangle); - --pst-icon-admonition-warning: var(--pst-icon-exclamation-triangle); - --pst-icon-admonition-danger: var(--pst-icon-exclamation-triangle); - --pst-icon-admonition-error: var(--pst-icon-times-circle); - --pst-icon-admonition-hint: var(--pst-icon-lightbulb); - --pst-icon-admonition-tip: var(--pst-icon-lightbulb); - --pst-icon-admonition-important: var(--pst-icon-exclamation-circle); - -} diff --git a/_preview/419/_static/custom.css b/_preview/419/_static/custom.css deleted file mode 100644 index 066fde3b2..000000000 --- a/_preview/419/_static/custom.css +++ /dev/null @@ -1,30 +0,0 @@ -kbd { - /* Based on: https://dylanatsmith.com/wrote/styling-the-kbd-element */ - - /* Key top color */ - background-color: rgb(238, 237, 237); - - /* Key legend */ - color: rgba(var(--pst-color-paragraph), 1); - font-size: 0.75em; - font-family: monospace; - font-weight: bold; - - /* Fancy border and shadow, to make it look like a key */ - border-radius: 0.3em; - border: 1px solid grey; - box-shadow: 0 2px 0 1px grey; - padding: 2px 5px; - margin-left: 1px; - margin-right: 1px; - line-height: 1; - position: relative; - top: -2px; - - cursor: default; /* instead of text selection cursor */ -} -kbd:hover { - /* Press-down on hover */ - box-shadow: 0 1px 0 0.5px grey; - top: 0px; -} diff --git a/_preview/419/_static/design-style.4045f2051d55cab465a707391d5b2007.min.css b/_preview/419/_static/design-style.4045f2051d55cab465a707391d5b2007.min.css deleted file mode 100644 index 3225661c2..000000000 --- a/_preview/419/_static/design-style.4045f2051d55cab465a707391d5b2007.min.css +++ /dev/null @@ -1 +0,0 @@ -.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative}details.sd-dropdown .sd-summary-title{font-weight:700;padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary{list-style:none;padding:1em}details.sd-dropdown summary .sd-octicon.no-title{vertical-align:middle}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown summary::-webkit-details-marker{display:none}details.sd-dropdown summary:focus{outline:none}details.sd-dropdown .sd-summary-icon{margin-right:.5em}details.sd-dropdown .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary:hover .sd-summary-up svg,details.sd-dropdown summary:hover .sd-summary-down svg{opacity:1;transform:scale(1.1)}details.sd-dropdown .sd-summary-up svg,details.sd-dropdown .sd-summary-down svg{display:block;opacity:.6}details.sd-dropdown .sd-summary-up,details.sd-dropdown .sd-summary-down{pointer-events:none;position:absolute;right:1em;top:1em}details.sd-dropdown[open]>.sd-summary-title .sd-summary-down{visibility:hidden}details.sd-dropdown:not([open])>.sd-summary-title .sd-summary-up{visibility:hidden}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #007bff;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0069d9;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem} diff --git a/_preview/419/_static/design-tabs.js b/_preview/419/_static/design-tabs.js deleted file mode 100644 index 36b38cf0d..000000000 --- a/_preview/419/_static/design-tabs.js +++ /dev/null @@ -1,27 +0,0 @@ -var sd_labels_by_text = {}; - -function ready() { - const li = document.getElementsByClassName("sd-tab-label"); - for (const label of li) { - syncId = label.getAttribute("data-sync-id"); - if (syncId) { - label.onclick = onLabelClick; - if (!sd_labels_by_text[syncId]) { - sd_labels_by_text[syncId] = []; - } - sd_labels_by_text[syncId].push(label); - } - } -} - -function onLabelClick() { - // Activate other inputs with the same sync id. - syncId = this.getAttribute("data-sync-id"); - for (label of sd_labels_by_text[syncId]) { - if (label === this) continue; - label.previousElementSibling.checked = true; - } - window.localStorage.setItem("sphinx-design-last-tab", syncId); -} - -document.addEventListener("DOMContentLoaded", ready, false); diff --git a/_preview/419/_static/doctools.js b/_preview/419/_static/doctools.js deleted file mode 100644 index e1bfd708b..000000000 --- a/_preview/419/_static/doctools.js +++ /dev/null @@ -1,358 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - this.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated === 'undefined') - return string; - return (typeof translated === 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated === 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) === 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - var url = new URL(window.location); - url.searchParams.delete('highlight'); - window.history.replaceState({}, '', url); - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar : function() { - $('input[name=q]').first().focus(); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this === '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) - return; - - $(document).keydown(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box, textarea, dropdown or button - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' - && activeElementType !== 'BUTTON') { - if (event.altKey || event.ctrlKey || event.metaKey) - return; - - if (!event.shiftKey) { - switch (event.key) { - case 'ArrowLeft': - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) - break; - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - break; - case 'ArrowRight': - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) - break; - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - break; - case 'Escape': - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) - break; - Documentation.hideSearchWords(); - return false; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case '/': - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) - break; - Documentation.focusSearchBar(); - return false; - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/_preview/419/_static/documentation_options.js b/_preview/419/_static/documentation_options.js deleted file mode 100644 index 877e3c311..000000000 --- a/_preview/419/_static/documentation_options.js +++ /dev/null @@ -1,14 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '', - LANGUAGE: 'None', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '', - NAVIGATION_WITH_KEYS: true, - SHOW_SEARCH_SUMMARY: true, - ENABLE_SEARCH_SHORTCUTS: true, -}; \ No newline at end of file diff --git a/_preview/419/_static/favicon.ico b/_preview/419/_static/favicon.ico deleted file mode 100644 index da6ac735a..000000000 Binary files a/_preview/419/_static/favicon.ico and /dev/null differ diff --git a/_preview/419/_static/file.png b/_preview/419/_static/file.png deleted file mode 100644 index a858a410e..000000000 Binary files a/_preview/419/_static/file.png and /dev/null differ diff --git a/_preview/419/_static/footer-logo-nsf.png b/_preview/419/_static/footer-logo-nsf.png deleted file mode 100644 index 11c788f2a..000000000 Binary files a/_preview/419/_static/footer-logo-nsf.png and /dev/null differ diff --git a/_preview/419/_static/images/logo_binder.svg b/_preview/419/_static/images/logo_binder.svg deleted file mode 100644 index 45fecf751..000000000 --- a/_preview/419/_static/images/logo_binder.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - -logo - - - - - - - - diff --git a/_preview/419/_static/images/logo_colab.png b/_preview/419/_static/images/logo_colab.png deleted file mode 100644 index b7560ec21..000000000 Binary files a/_preview/419/_static/images/logo_colab.png and /dev/null differ diff --git a/_preview/419/_static/images/logo_jupyterhub.svg b/_preview/419/_static/images/logo_jupyterhub.svg deleted file mode 100644 index 60cfe9f22..000000000 --- a/_preview/419/_static/images/logo_jupyterhub.svg +++ /dev/null @@ -1 +0,0 @@ -logo_jupyterhubHub diff --git a/_preview/419/_static/jquery-3.5.1.js b/_preview/419/_static/jquery-3.5.1.js deleted file mode 100644 index 50937333b..000000000 --- a/_preview/419/_static/jquery-3.5.1.js +++ /dev/null @@ -1,10872 +0,0 @@ -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " -{% endmacro %} \ No newline at end of file diff --git a/_preview/419/appendix/how-to-contribute.html b/_preview/419/appendix/how-to-contribute.html deleted file mode 100644 index d006362a0..000000000 --- a/_preview/419/appendix/how-to-contribute.html +++ /dev/null @@ -1,949 +0,0 @@ - - - - - - - - Pythia Foundations Contributor’s Guide — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Pythia Foundations Contributor’s Guide

-
-

Note

-

This content is under construction!

-
-

General information on how to contribute to any Project Pythia repository -may be found here.

-

This page will eventually contain a full guide to contributing to Project Pythia. As GitHub Pull Requests are an important part of contributing to Pythia, this guide will cross-reference tutorials on GitHub and Pull Requests.

-

If you need to comment on anything in Pythia Foundations you feel needs work, you can use the “open issue” or “suggest edit” buttons at the top of any Pythia Foundations page. These buttons appear when you hover over the GitHub Octocat logo. Clicking on these buttons will take you to the relevant page on GitHub, where the entirety of the Pythia Foundations material is hosted. In order to actually suggest changes, you must have a free GitHub account, as listed in the GitHub section of Pythia Foundations. This contributor’s guide is strictly for Pythia Foundations; for general Project Pythia contribution guidelines, see the main Project Pythia Contributor’s Guide.

-

To quickly provide feedback about minor issues without the use of GitHub, you can also use this Google Form.

-
-

Contributing a new Jupyter Notebook

-

If you’d like to contribute a Jupyter Notebook to these materials, please reference our template viewable on the next page. This template is available to you in appendix/template.ipynb if you’ve cloned the source repository, or available as a download directly from GitHub.

-
-
-

Building the site

-
-

Create a conda environment

-

The first time you check out this repository, run:

-
conda env update -f environment.yml
-
-
-

This will create or update the dev environment (pythia-book-dev).

-
-
-

Install pre-commit hooks

-

This repository includes pre-commit hooks (defined in .pre-commit-config.yaml). To activate/install these pre-commit hooks, run:

-
conda activate pythia-book-dev
-pre-commit install
-
-
-

This is also a one-time step.

-

NOTE: The pre-commit package is already installed via the pythia-book-dev conda environment.

-
-
-

Building the book locally

-

To build the book locally, run the following:

-
conda activate pythia-book-dev
-jupyter-book build .
-
-
-

Finally, you can view the book by opening the file _build/html/index.html with your favorite web browser. On most platforms you can simply run:

-
open _build/html/index.html
-
-
-
-
-

Keeping your dev environment up to date

-

It’s good practice to update the packages in your pythia-book-dev conda environment frequently to their latest versions, especially if it’s been a while since you used it. If the jupyter-book build . command above generates error messages, that is a good indication that your conda environment may be out of date.

-

To update all packages in the currently activated environment to their latest versions, do this:

-
conda update --all
-
-
-
-
-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/appendix/template.html b/_preview/419/appendix/template.html deleted file mode 100644 index 7021c5bf0..000000000 --- a/_preview/419/appendix/template.html +++ /dev/null @@ -1,1147 +0,0 @@ - - - - - - - - Project Pythia Notebook Template — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -
-

Project Pythia Notebook Template

-
-

How to Use This Page

-

This page is designed as a template. As such, each section contains instructions for the content added to the equivalent section of a new notebook, with the exception of this section, and the Setting Up a New Notebook section. Because this is not a tutorial, the overall structure of the page does not need to be cohesive.

-
-
-

Setting Up a New Notebook

-

This section lists the first steps for configuring a Jupyter Notebook for inclusion in Pythia Foundations. First, if you have an image relevant to your notebook, such as a logo, link to this image at the top of the notebook. The following Markdown example illustrates the correct technique for linking such an image:

-
-

![<image title>](http://link.com/to/image.png "image alt text")

-
-

You can also use an img tag in raw HTML to embed your logo or other image. Second, make sure to add an HTML alt tag to any image in your notebook. This includes any type of image, including logos, wherever and however they appear in your notebook. Adding this tag improves accessibility and allows more people to properly access your notebook.

-

Project Pythia Logo

-
-
-

Project Pythia Notebook Template

-

Each notebook must be properly titled with a top level Markdown header, i.e., a header title prefixed by a single # mark. Nowhere else in the notebook should you use a top level header. This header will be automatically used by the Pythia book-building process to generate the page title, which will then be added to the navbar, table of contents, etc. As such, the header needs to be short, concise, and descriptive. After the header line, add a separate Jupyter Notebook cell with the text ---. This adds a separating line used to separate the title from the overview and prerequisites. This technique will also be used later to separate other sections.

-
-
-
-

Overview

-

If your notebook contains an introductory paragraph, include the paragraph at the start of this section. Such paragraphs must be short, and relevant to the content of the notebook. After the introductory paragraph, it is required to list the notebook topics, in the format shown below:

-
    -
  1. This is a numbered list of the specific topics

  2. -
  3. These should map approximately to your main sections of content

  4. -
  5. Or each second-level, ##, header in your notebook

  6. -
  7. Keep the size and scope of your notebook in check

  8. -
  9. And be sure to let the reader know up front the important concepts they’ll be leaving with

  10. -
-
-
-

Prerequisites

-

This part of the Pythia Notebook Template was inspired by another template; in this case, the template for the Jupyter Book known as The Turing Way.

-

Following the overview section, the prerequisites section must enumerate a list of concepts and Python packages. These concepts and packages must comprise the knowledge that readers of your notebook must know and understand in order to successfully learn the notebook material. Each concept or package listed must link to a Pythia Foundations tutorial, or to a relevant external resource. To build the prerequisite table, first copy the following Markdown table into your notebook. You must then edit the table to contain your notebook prerequisites. Each row must contain the name of the concept, along with a link to the tutorial, either on Pythia Foundations or a relevant external resource. It must also be noted whether the concept is helpful or necessary.

- - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Intro to Cartopy

Necessary

Understanding of NetCDF

Helpful

Familiarity with metadata structure

Project management

Helpful

-
    -
  • Time to learn: You must provide an estimate of the total time to learn the listed concepts. The general rule is to estimate 5 minutes for each subsection in each concept, or 10 minutes for especially lengthy subsections. Add the estimates for each subsection to obtain the time to learn. Also, please note that overestimates are better than underestimates.

  • -
  • System requirements:

    -
      -
    • If there are any system, version, or non-Python software requirements for the material in your notebook, these must be listed in a system requirement list.

    • -
    • If your notebook has no extra requirements, the System Requirements section should be completely removed.

    • -
    • Note that Python packages do not count as system requirements; these should be listed in the Imports section.

    • -
    -
  • -
-
-
-
-

Imports

-

Before beginning this section, add a Markdown cell with a --- divider. This section should list import statements for any Python packages required for your notebook content. Optionally, you can include a description above the code cell as well.

-
-
-
import sys
-
-
-
-
-
-
-

Your first content section

-

Replace this template section with your first section of tutorial material; all tutorial material should roughly match up with the objectives stated in the Overview section. Your notebook sections should be laid out as a narrative, each containing interspersed Markdown text, images, code cells, and other content as necessary.

-
-
-
# Code cells like this are an essential part of your notebook
-print("Hello world!")
-
-
-
-
-
Hello world!
-
-
-
-
-
-

A content subsection

-

To provide more detail about concepts in content sections, it is recommended to create content subsections. As shown in this template section, subsections are added through lower-level Markdown headers, and automatically populate navbars, both when viewing the notebook in JupyterLab and when viewing the notebook as a Pythia Foundations tutorial page.

-
-
-
# some subsection code
-new = "helpful information"
-
-
-
-
-
-
-

Another content subsection

-

This subsection was created in the same way as the previous subsection. Subsections often contain detailed information relevant to the material. An example relevant to this template is “Try to avoid using code comments as narrative; instead, let them only exist for brief clarification as needed.”

-
-
-
-

Your second content section

-

The second content section should roughly match up with the second learning objective of your notebook. For this template, the objective in question is to learn levels of Markdown headers. Below is a demonstration of Markdown header levels; however, be aware that each new header is incorporated into the navbars.

-
-

This example is

-
-

a quick demonstration

-
-
of further and further
-
-
header levels
-

Each section in your notebook can also contain \(\LaTeX\) equations, enabled through MathJax. In the following example, we illustrate some sample MathJax equations. (Rendering instructions, as well as detailed information about MathJax, can be found in this documentation.)

-
-(1)\[\begin{align} -\dot{x} & = \sigma(y-x) \\ -\dot{y} & = \rho x - y - xz \\ -\dot{z} & = -\beta z + xy -\end{align}\]
-

There are many helpful resources for learning Markdown and customizing Jupyter Markdown cells listed on this useful guide. In addition, there is information on formatting relevant specifically to Jupyter on this Jupyter documentation page. Finally, perfectionism is encouraged in Pythia Foundations, and there are many available resources for formatting notebooks in a perfectionistic manner.

-
-
-
-
-
-
-

Last Section

-

It is possible to embed raw HTML into Jupyter Markdown cells, as shown above with the Project Pythia logo. This allows for many forms of additional content; the most used form in Pythia is message boxes, as illustrated below. (If you are viewing this page as a Jupyter Notebook, you can also edit the following Markdown cell to view the underlying code.)

-
-

Info

-

This is an info box. Info boxes contain additional information about tutorial concepts.

-
-

Making a notebook for Pythia inevitably requires some trial and error for formatting, among other things. If you feel the formatting is lacking in some way, feel free to adjust it in different ways until it is up to your standards. Copying and editing Markdown cells is a good way to try different formatting options.

-

In addition, there are other types of boxes, known as admonitions, that can be inserted into a tutorial page:

-
-

Success

-

This is a success box. Success boxes are usually placed at the end of a set of examples, and usually show a message relating to the final state of the examples.

-
-
-

Warning

-

This is a warning box. Warning boxes are usually used to indicate a situation where making a mistake, such as a typo, can cause issues with the tutorial content.

-
-
-

Danger

-

This is a danger box. Danger boxes are usually used to indicate a situation where making a mistake, such as a typo, can cause more serious issues such as loss of data.

-
-

In addition, it is helpful and highly recommended to add cell tags to your Jupyter cells. These tags allow for customization of content display, especially for code cells. In addition, cell tags provide a means for demonstrating errors without breaking any production environments. If you are unfamiliar with cell tags, you can review this brief demonstration provided by Jupyter Book; this demonstration covers cell tags in Jupyter Notebook and Jupyter Lab, as well as fully manual cell tags.

-
-
-
-

Summary

-

Before adding a summary, you must first add another Markdown cell containing ---, which marks the end of the content body. A good Summary section contains a brief single paragraph that summarizes the tutorial content. The key content elements and their relation to the tutorial objectives should also be covered. Finally, the most important concepts should be listed again in detail.

-
-

What’s next?

-

This section should briefly describe the content in the page following your tutorial sequentially. You can find the page sequentially following yours using the Next link at the bottom of the page, or using the sidebar; Jupyter Book should pre-populate this. In addition, if your tutorial leads into other Pythia Foundations content, or tutorials found outside Pythia Foundations, these other tutorials can be linked to as well.

-
-
-
-

Resources and references

-

In this section, you must provide detailed citations and references to any external content used in your tutorial. Many types of external content are designed in as much detail as Pythia Foundations pages, and crediting the author is essential. In addition, this section can contain links to additional external content, such as reading, documentation, etc. Once this section is complete, your notebook is finished. After giving your new notebook a quick review, you can request the addition of the notebook to Pythia Foundations by sending the team a GitHub Pull Request. Here are a few final notes pertaining to working with Jupyter and Pythia:

-
    -
  • In order to confirm that your notebook runs from start to finish without errors, hangs, etc., go to the Kernel menu in Jupyter Lab and select Restart Kernel and Run All Cells.

  • -
  • In order to prepare your notebook to be committed to Pythia Foundations, go to the Kernel menu in Jupyter Lab and select Restart Kernel and Clear All Outputs. After the notebook is committed, the Jupyter cells will be run and optimized for Pythia automatically.

  • -
  • If you wish to take credit for your notebook, you can add contact information in this section; this is completely optional.

  • -
  • It is very important that any code, information, images, etc. referenced in the above sections of your notebook contains appropriate attribution of authorship in this section.

  • -
  • Finally, it is imperative that you must have a legal right to use any content included in your notebook. Do not commit copyright infringement or plagiarism.

  • -
-

The Project Pythia team thanks you greatly for contributing to Pythia Foundations.

-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/cartopy.html b/_preview/419/core/cartopy.html deleted file mode 100644 index 7d7f2cdc4..000000000 --- a/_preview/419/core/cartopy.html +++ /dev/null @@ -1,862 +0,0 @@ - - - - - - - - Cartopy — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -
-

Cartopy

-

This section contains tutorials on plotting maps with Cartopy; it is cross-referenced with tutorials on Xarray and Matplotlib.

-
-

From the Cartopy website:

-
-

Cartopy is a Python package designed for geospatial data processing in order to -produce maps and other geospatial data analyses.

-

Cartopy makes use of the powerful PROJ.4, NumPy and Shapely libraries and includes a programmatic interface -built on top of Matplotlib for the creation of publication quality maps.

-

Key features of Cartopy are its object-oriented projection definitions, -and its ability to transform points, lines, vectors, polygons and images between those projections.

-
-

Before working through the Cartopy notebooks in this section of Pythia Foundations, you should first have a basic knowledge of Matplotlib.

-

In addition, please note that the geographic-features library used by Cartopy makes use of shapefiles directly served by Natural Earth.

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/cartopy/cartopy.html b/_preview/419/core/cartopy/cartopy.html deleted file mode 100644 index a18e1f888..000000000 --- a/_preview/419/core/cartopy/cartopy.html +++ /dev/null @@ -1,1525 +0,0 @@ - - - - - - - - Introduction to Cartopy — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - ../../_images/cartopy_logo.png -
-

Introduction to Cartopy

-
-
-

Overview

-

The concepts covered in this section include:

-
    -
  1. Learning core Cartopy concepts: map projections and GeoAxes

  2. -
  3. Exploring some of Cartopy’s map projections

  4. -
  5. Creating regional maps

  6. -
-

This tutorial will lead you through some basics of creating maps with specified projections using Cartopy, and adding geographical features (like coastlines and borders) to those maps.

-

Plotting data on map projections will be covered in later tutorials.

-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Matplotlib

Necessary

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

Imports

-

Here, we import the main libraries of Cartopy: crs and feature. In addition, we import numpy, as well as matplotlib’s pyplot interface. Finally, we import a library called warnings, and use it to remove extraneous warnings that Cartopy produces in later examples.

-
-
-
import warnings
-
-import matplotlib.pyplot as plt
-import numpy as np
-from cartopy import crs as ccrs, feature as cfeature
-
-#  Suppress warnings issued by Cartopy when downloading data files
-warnings.filterwarnings('ignore')
-
-
-
-
-
-
-
-

Basic concepts: map projections and GeoAxes

-
-

Extend Matplotlib’s axes into georeferenced GeoAxes

-

Recall from earlier tutorials that a figure in Matplotlib has two elements: a Figure object, and a list of one or more Axes objects (subplots).

-

Since we imported cartopy.crs, we now have access to Cartopy’s Coordinate Reference System, which contains many geographical projections. We can specify one of these projections for an Axes object to convert it into a GeoAxes object. This will effectively georeference the subplot. Examples of converting Axes objects into GeoAxes objects can be found later in this section.

-
-
-

Create a map with a specified projection

-

In this example, we’ll create a GeoAxes object that uses the PlateCarree projection. PlateCarree is a global lat-lon map projection in which each point is evenly spaced in terms of degrees. The name “Plate Carree” is French for “flat square”.

-
-
-
fig = plt.figure(figsize=(11, 8.5))
-ax = plt.subplot(1, 1, 1, projection=ccrs.PlateCarree(central_longitude=-75))
-ax.set_title("A Geo-referenced subplot, Plate Carree projection");
-
-
-
-
-../../_images/464f2151626b2d1163b904844680d14e39abf383bff9e2359b126f9da1f0a41d.png -
-
-

Although the figure seems empty, it has, in fact, been georeferenced using a map projection; this projection is provided by Cartopy’s crs (coordinate reference system) class. We can now add in cartographic features, in the form of shapefiles, to our subplot. One such cartographic feature is coastlines, which can be added to our subplot using the callable GeoAxes method simply called coastlines.

-
-
-
ax.coastlines()
-
-
-
-
-
<cartopy.mpl.feature_artist.FeatureArtist at 0x7f50cbead090>
-
-
-
-
-
-

Info

-

To get the figure to display again with the features that we’ve added since the original display, just type the name of the Figure object in its own cell.

-
-
-
-
fig
-
-
-
-
-../../_images/b699d49a5bd056025b7e0fe8189789d4bbec17a114c88b378dc77f813d98bc60.png -
-
-
-
-

Add cartographic features to the map

-

Cartopy provides other cartographic features via its features class, which was imported at the beginning of this page, under the name cfeature. These cartographic features are laid out as data in shapefiles. The shapefiles are downloaded when their cartographic features are used for the first time in a script or notebook, and they are downloaded from https://www.naturalearthdata.com/. Once downloaded, they “live” in your ~/.local/share/cartopy directory (note the ~ represents your home directory).

-

We can add these features to our subplot via the add_feature method; this method allows the definition of attributes using arguments, similar to Matplotlib’s plot method. A list of the various Natural Earth shapefiles can be found at https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html. In this example, we add borders and U. S. state lines to our subplot:

-
-
-
ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='black')
-ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')
-
-
-
-
-
<cartopy.mpl.feature_artist.FeatureArtist at 0x7f50cbdd7d90>
-
-
-
-
-

Once again, referencing the Figure object will re-render the figure in the notebook, now including the two features.

-
-
-
fig
-
-
-
-
-../../_images/971ed4b4413428bf02950c94e52aecf7252af060399486372f9f151b94447d11.png -
-
-
-
-
-

Explore some of Cartopy’s map projections

-
-

Info

-

You can find a list of supported projections in Cartopy, with examples, at https://scitools.org.uk/cartopy/docs/latest/reference/crs.html

-
-
-

Mollweide Projection (often used with global satellite mosaics)

-

To save typing later, we can define a projection object to store the definition of the map projection. We can then use this object in the projection kwarg of the subplot method when creating a GeoAxes object. This allows us to use this exact projection in later scripts or Jupyter Notebook cells using simply the object name, instead of repeating the same call to ccrs.

-
-
-
fig = plt.figure(figsize=(11, 8.5))
-projMoll = ccrs.Mollweide(central_longitude=0)
-ax = plt.subplot(1, 1, 1, projection=projMoll)
-ax.set_title("Mollweide Projection")
-
-
-
-
-
Text(0.5, 1.0, 'Mollweide Projection')
-
-
-../../_images/fbfaaaebf1974140e69d13218d4a8a939db0f64462fc826d3ce36c144c84077c.png -
-
-
-

Add in the cartographic shapefiles

-

This example shows how to add cartographic features to the Mollweide projection defined earlier:

-
-
-
ax.coastlines()
-ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue')
-fig
-
-
-
-
-../../_images/1198a97c57a1fee9f869d7fd1e711eeb906948d4e17a602d8745405c27a71570.png -
-
-
-
-

Add a fancy background image to the map.

-

We can also use the stock_img method to add a pre-created background to a Mollweide-projection plot:

-
-
-
ax.stock_img()
-fig
-
-
-
-
-../../_images/cedcd403574b1a4da611754fd42bf6b165b8c6f822dc3d6f0293e404facd4597.png -
-
-
-
-
-

Lambert Azimuthal Equal Area Projection

-

This example is similar to the above example set, except it uses a Lambert azimuthal equal-area projection instead:

-
-
-
fig = plt.figure(figsize=(11, 8.5))
-projLae = ccrs.LambertAzimuthalEqualArea(central_longitude=0.0, central_latitude=0.0)
-ax = plt.subplot(1, 1, 1, projection=projLae)
-ax.set_title("Lambert Azimuthal Equal Area Projection")
-ax.coastlines()
-ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');
-
-
-
-
-../../_images/3d5792234feec41bc1d53c761f5d1b6b529723772e4b6cdfba5f56fbb9e1e59a.png -
-
-
-
-
-

Create regional maps

-
-

Cartopy’s set_extent method

-

For this example, let’s create another PlateCarree projection, but this time, we’ll use Cartopy’s set_extent method to restrict the map coverage to a North American view. Let’s also choose a lower resolution for coastlines, just to illustrate how one can specify that. In addition, let’s also plot the latitude and longitude lines.

-

Natural Earth defines three resolutions for cartographic features, specified as the strings “10m”, “50m”, and “110m”. Only one resolution can be used at a time, and the higher the number, the less detailed the feature becomes. You can view the documentation for this functionality at the following reference link: https://www.naturalearthdata.com/downloads/

-
-
-
projPC = ccrs.PlateCarree()
-lonW = -140
-lonE = -40
-latS = 15
-latN = 65
-cLat = (latN + latS) / 2
-cLon = (lonW + lonE) / 2
-res = '110m'
-
-
-
-
-
-
-
fig = plt.figure(figsize=(11, 8.5))
-ax = plt.subplot(1, 1, 1, projection=projPC)
-ax.set_title('Plate Carree')
-gl = ax.gridlines(
-    draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'
-)
-ax.set_extent([lonW, lonE, latS, latN], crs=projPC)
-ax.coastlines(resolution=res, color='black')
-ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')
-ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');
-
-
-
-
-../../_images/f15e5a34ed98fe236412f1bd9cd8acc64e0bcd569c7c06f4088e3233e385ff0a.png -
-
-
-

Info

-

Please note, even though the calls to the subplot method use different projections, the calls to set_extent use PlateCarree. This ensures that the values we passed into set_extent will be transformed from degrees into the values appropriate for the projection we use for the map.

-
-

The PlateCarree projection exaggerates the spatial extent of regions closer to the poles. In the following examples, we use set_extent with stereographic and Lambert-conformal projections, which display polar regions more accurately.

-
-
-
projStr = ccrs.Stereographic(central_longitude=cLon, central_latitude=cLat)
-fig = plt.figure(figsize=(11, 8.5))
-ax = plt.subplot(1, 1, 1, projection=projStr)
-ax.set_title('Stereographic')
-gl = ax.gridlines(
-    draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'
-)
-ax.set_extent([lonW, lonE, latS, latN], crs=projPC)
-ax.coastlines(resolution=res, color='black')
-ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')
-ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');
-
-
-
-
-../../_images/82af062524018f51120101e6a79930855059ebc0757960507c23b268176b24c6.png -
-
-
-
-
projLcc = ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)
-fig = plt.figure(figsize=(11, 8.5))
-ax = plt.subplot(1, 1, 1, projection=projLcc)
-ax.set_title('Lambert Conformal')
-gl = ax.gridlines(
-    draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle='--'
-)
-ax.set_extent([lonW, lonE, latS, latN], crs=projPC)
-ax.coastlines(resolution='110m', color='black')
-ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor='brown')
-# End last line with a semicolon to suppress text output to the screen
-ax.add_feature(cfeature.BORDERS, linewidth=0.5, edgecolor='blue');
-
-
-
-
-../../_images/ca23e937b43bda17611e6fc03fb58db631f0f9ec48a32a5f4a2cc78290e9aec4.png -
-
-
-

Info

-

Lat/lon labeling for projections other than Mercator and PlateCarree is a recent addition to Cartopy. As you can see, work still needs to be done to improve the placement of labels.

-
-
-
-

Create a regional map centered over New York State

-

Here we set the domain, which defines the geographical region to be plotted. (This is used in the next section in a set_extent call.) Since these coordinates are expressed in degrees, they correspond to a PlateCarree projection, even though the map projection is set to LambertConformal.

-
-

Warning

-

Be patient; when plotting a small geographical area, the high-resolution “10m” shapefiles are used by default. As a result, these plots take longer to create, especially if the shapefiles are not yet downloaded from Natural Earth. Similar issues can occur whenever a GeoAxes object is transformed from one coordinate system to another. (This will be covered in more detail in a subsequent page.)

-
-
-
-
latN = 45.2
-latS = 40.2
-lonW = -80.0
-lonE = -71.5
-cLat = (latN + latS) / 2
-cLon = (lonW + lonE) / 2
-projLccNY = ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)
-
-
-
-
-
-
-

Add some predefined features

-

Some cartographical features are predefined as constants in the cartopy.feature package. The resolution of these features depends on the amount of geographical area in your map, specified by set_extent.

-
-
-
fig = plt.figure(figsize=(15, 10))
-ax = plt.subplot(1, 1, 1, projection=projLccNY)
-ax.set_extent([lonW, lonE, latS, latN], crs=projPC)
-ax.set_facecolor(cfeature.COLORS['water'])
-ax.add_feature(cfeature.LAND)
-ax.add_feature(cfeature.COASTLINE)
-ax.add_feature(cfeature.BORDERS, linestyle='--')
-ax.add_feature(cfeature.LAKES, alpha=0.5)
-ax.add_feature(cfeature.STATES)
-ax.add_feature(cfeature.RIVERS)
-ax.set_title('New York and Vicinity');
-
-
-
-
-../../_images/2350da468e0aafc840b14986a53fc2d1fadf0666cc90ee622a8e1e00105e6cd7.png -
-
-
-

Note:

-

For high-resolution Natural Earth shapefiles such as this, while we could add Cartopy’s OCEAN feature, it currently takes much longer to render on the plot. You can create your own version of this example, with the OCEAN feature added, to see for yourself how much more rendering time is added. Instead, we take the strategy of first setting the facecolor of the entire subplot to match that of water bodies in Cartopy. When we then layer on the LAND feature, pixels that are not part of the LAND shapefile remain in the water facecolor, which is the same color as the OCEAN.

-
-
-
-

Use lower-resolution shapefiles from Natural Earth

-

In this example, we create a new map. This map uses lower-resolution shapefiles from Natural Earth, and also eliminates the plotting of country borders.

-

This example requires much more code than previous examples on this page. First, we must create new objects associated with lower-resolution shapefiles. This is performed by the NaturalEarthFeature method, which is part of the Cartopy feature class. Second, we call add_feature to add the new objects to our new map.

-
-
-
fig = plt.figure(figsize=(15, 10))
-ax = plt.subplot(1, 1, 1, projection=projLccNY)
-ax.set_extent((lonW, lonE, latS, latN), crs=projPC)
-
-# The features with names such as cfeature.LAND, cfeature.OCEAN, are higher-resolution (10m)
-# shapefiles from the Naturalearth repository.  Lower resolution shapefiles (50m, 110m) can be
-# used by using the cfeature.NaturalEarthFeature method as illustrated below.
-
-resolution = '110m'
-
-land_mask = cfeature.NaturalEarthFeature(
-    'physical',
-    'land',
-    scale=resolution,
-    edgecolor='face',
-    facecolor=cfeature.COLORS['land'],
-)
-sea_mask = cfeature.NaturalEarthFeature(
-    'physical',
-    'ocean',
-    scale=resolution,
-    edgecolor='face',
-    facecolor=cfeature.COLORS['water'],
-)
-lake_mask = cfeature.NaturalEarthFeature(
-    'physical',
-    'lakes',
-    scale=resolution,
-    edgecolor='face',
-    facecolor=cfeature.COLORS['water'],
-)
-state_borders = cfeature.NaturalEarthFeature(
-    category='cultural',
-    name='admin_1_states_provinces_lakes',
-    scale=resolution,
-    facecolor='none',
-)
-
-ax.add_feature(land_mask)
-ax.add_feature(sea_mask)
-ax.add_feature(lake_mask)
-ax.add_feature(state_borders, linestyle='solid', edgecolor='black')
-ax.set_title('New York and Vicinity; lower resolution');
-
-
-
-
-../../_images/509c90705817ac348925e3a22bcb7158d49e960c9c6314bef388658e0f72aacd.png -
-
-
-
-

A figure with two different regional maps

-

Finally, let’s create a figure with two subplots. On the first subplot, we’ll repeat the high-resolution New York State map created earlier; on the second, we’ll plot over a different part of the world.

-
-
-
# Create the figure object
-fig = plt.figure(
-    figsize=(30, 24)
-)  # Notice we need a bigger "canvas" so these two maps will be of a decent size
-
-# First subplot
-ax = plt.subplot(2, 1, 1, projection=projLccNY)
-ax.set_extent([lonW, lonE, latS, latN], crs=projPC)
-ax.set_facecolor(cfeature.COLORS['water'])
-ax.add_feature(cfeature.LAND)
-ax.add_feature(cfeature.COASTLINE)
-ax.add_feature(cfeature.BORDERS, linestyle='--')
-ax.add_feature(cfeature.LAKES, alpha=0.5)
-ax.add_feature(cfeature.STATES)
-ax.set_title('New York and Vicinity')
-
-# Set the domain for defining the second plot region.
-latN = 70
-latS = 30.2
-lonW = -10
-lonE = 50
-cLat = (latN + latS) / 2
-cLon = (lonW + lonE) / 2
-
-projLccEur = ccrs.LambertConformal(central_longitude=cLon, central_latitude=cLat)
-
-# Second subplot
-ax2 = plt.subplot(2, 1, 2, projection=projLccEur)
-ax2.set_extent([lonW, lonE, latS, latN], crs=projPC)
-ax2.set_facecolor(cfeature.COLORS['water'])
-ax2.add_feature(cfeature.LAND)
-ax2.add_feature(cfeature.COASTLINE)
-ax2.add_feature(cfeature.BORDERS, linestyle='--')
-ax2.add_feature(cfeature.LAKES, alpha=0.5)
-ax2.add_feature(cfeature.STATES)
-ax2.set_title('Europe');
-
-
-
-
-../../_images/3888f06f49b43a68a08a89039b581d771054ae22ed348bdd167f04deb15a1786.png -
-
-
-
-
-

An example of plotting data

-

First, we’ll create a lat-lon grid and define some data on it.

-
-
-
lon, lat = np.mgrid[-180:181, -90:91]
-data = 2 * np.sin(3 * np.deg2rad(lon)) + 3 * np.cos(4 * np.deg2rad(lat))
-plt.contourf(lon, lat, data)
-plt.colorbar();
-
-
-
-
-../../_images/f28790b92986c53664cd94ddbf27f0047969b83e9e257e3fdcaa799f1f4bf891.png -
-
-

Plotting data on a Cartesian grid is equivalent to plotting data in the PlateCarree projection, where meridians and parallels are all straight lines with constant spacing. As a result of this simplicity, the global datasets we use often begin in the PlateCarree projection.

-

Once we create our map again, we can plot these data values as a contour map. We must also specify the transform keyword argument. This is an argument to a contour-plotting method that specifies the projection type currently used by our data. The projection type specified by this argument will be transformed into the projection type specified in the subplot method. Let’s plot our data in the Mollweide projection to see how shapes change under a transformation.

-
-
-
fig = plt.figure(figsize=(11, 8.5))
-ax = plt.subplot(1, 1, 1, projection=projMoll)
-ax.coastlines()
-dataplot = ax.contourf(lon, lat, data, transform=ccrs.PlateCarree())
-plt.colorbar(dataplot, orientation='horizontal');
-
-
-
-
-../../_images/437f45eb897aa23ad39e8dea8db03b632cae1df0733aabfb52f5105748ba4d2c.png -
-
-
-
-
-

Summary

-
    -
  • Cartopy allows for the georeferencing of Matplotlib Axes objects.

  • -
  • Cartopy’s crs class supports a variety of map projections.

  • -
  • Cartopy’s feature class allows for a variety of cartographic features to be overlaid on a georeferenced plot or subplot.

  • -
-
-
-
-

What’s Next?

-

In the next notebook, we will delve further into how one can transform data that is defined in one coordinate reference system (crs) so it displays properly on a map that uses a different crs.

-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/data-formats.html b/_preview/419/core/data-formats.html deleted file mode 100644 index 87abb89b1..000000000 --- a/_preview/419/core/data-formats.html +++ /dev/null @@ -1,854 +0,0 @@ - - - - - - - - Data Formats — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -
-

Data Formats

-
-

Note

-

This content is under construction!

-
-

There are many data file formats used commonly in the geosciences, such as NetCDF and GRIB. This section contains tutorials on how to interact with these files in Python.

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/data-formats/netcdf-cf.html b/_preview/419/core/data-formats/netcdf-cf.html deleted file mode 100644 index 2ec302b21..000000000 --- a/_preview/419/core/data-formats/netcdf-cf.html +++ /dev/null @@ -1,1695 +0,0 @@ - - - - - - - - NetCDF and CF: The Basics — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -

NetCDF Logo

-
-

NetCDF and CF: The Basics

-
-
-

Overview

-

This tutorial will begin with an introduction to netCDF. The CF data model will then be covered, and finally, important implementation details for netCDF. The structure of the tutorial is as follows:

-
    -
  1. Demonstrating gridded data

  2. -
  3. Demonstrating observational data

  4. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Numpy Basics

Necessary

Datetime

Necessary

-
    -
  • Time to learn: 50 minutes

  • -
-
-
-
-

Imports

-

Some of these imports will be familiar from previous tutorials. However, some of them likely look foreign; these will be covered in detail later in this tutorial.

-
-
-
from datetime import datetime, timedelta
-
-import numpy as np
-from cftime import date2num
-from netCDF4 import Dataset
-from pyproj import Proj
-
-
-
-
-

-
-
-

Gridded Data

-

Let’s say we’re working with some numerical weather forecast model output. First, we need to store the data in the netCDF format. Second, we need to ensure that the metadata follows the Climate and Forecasting conventions. These steps ensure that a dataset is available to as many scientific data tools as is possible. The examples in this section illustrate these steps in detail.

-

To start, let’s assume the following about our data:

-
    -
  • There are three spatial dimensions (x, y, and press) and one temporal dimension (times).

  • -
  • The native coordinate system of the model is on a regular 3km x 3km grid (x and y) that represents the Earth on a Lambert conformal projection.

  • -
  • The vertical dimension (press) consists of several discrete pressure levels in units of hPa.

  • -
  • The time dimension consists of twelve consecutive hours (times), beginning at 2200 UTC on the current day.

  • -
-

The following code generates the dimensional arrays just discussed:

-
-
-
start = datetime.utcnow().replace(hour=22, minute=0, second=0, microsecond=0)
-times = np.array([start + timedelta(hours=h) for h in range(13)])
-
-x = np.arange(-150, 153, 3)
-y = np.arange(-100, 100, 3)
-
-press = np.array([1000, 925, 850, 700, 500, 300, 250])
-
-
-
-
-

In addition to dimensional arrays, we also need a variable of interest, which holds the data values at each unique dimensional index. In these examples, this variable is called temps, and holds temperature data. Note that the dimensions correspond to the ones we just created above.

-
-
-
temps = np.random.randn(times.size, press.size, y.size, x.size)
-
-
-
-
-
-

Creating the file and dimensions

-

The first step in setting up a new netCDF file is to create a new file in netCDF format and set up the shared dimensions we’ll be using in the file. We’ll be using the netCDF4 library to do all of the requisite netCDF API calls.

-
-
-
nc = Dataset('forecast_model.nc', 'w', format='NETCDF4_CLASSIC', diskless=True)
-
-
-
-
-
-

Info

-

The netCDF file created in the above example resides in memory, not disk, due to the diskless=True argument. In order to create this file on disk, you must either remove this argument, or add the persist=True argument.

-
-
-

Danger

-

If you open an existing file with ‘w’ as the second argument, any data already in the file will be overwritten. If you would like to edit the file, or add to it, open it using ‘a’ as the second argument.

-
-

We start the setup of this new netCDF file by creating and adding global attribute metadata. These particular metadata elements are not required, but are recommended by the CF standard. In addition, adding these elements to the file is simple, and helps users keep track of the data. Therefore, it is helpful to add these metadata elements, as shown below:

-
-
-
nc.Conventions = 'CF-1.7'
-nc.title = 'Forecast model run'
-nc.institution = 'Unidata'
-nc.source = 'WRF-1.5'
-nc.history = str(datetime.utcnow()) + ' Python'
-nc.references = ''
-nc.comment = ''
-
-
-
-
-

This next example shows a plain-text representation of our netCDF file as it exists currently:

-
netcdf forecast_model {
-  attributes:
-    :Conventions = "CF-1.7" ;
-    :title = "Forecast model run" ;
-    :institution = "Unidata" ;
-    :source = "WRF-1.5" ;
-    :history = "2019-07-16 02:21:52.005718 Python" ;
-    :references = "" ;
-    :comment = "" ;
-}
-
-
-
-

Info

-

This plain-text representation is known as netCDF Common Data Format Language, or CDL.

-
-

Variables are an important part of every netCDF file; they are used to define data fields. However, before we can add any variables to our file, we must first define the dimensions of the data. In this example, we create dimensions called x, y, and pressure, and set the size of each dimension to the size of the corresponding data array. We then create an additional dimension, forecast_time, and set the size as None. This defines the dimension as “unlimited”, meaning that if additional data values are added later, the netCDF file grows along this dimension.

-
-
-
nc.createDimension('forecast_time', None)
-nc.createDimension('x', x.size)
-nc.createDimension('y', y.size)
-nc.createDimension('pressure', press.size)
-nc
-
-
-
-
-
<class 'netCDF4._netCDF4.Dataset'>
-root group (NETCDF4_CLASSIC data model, file format HDF5):
-    Conventions: CF-1.7
-    title: Forecast model run
-    institution: Unidata
-    source: WRF-1.5
-    history: 2023-08-08 23:18:59.874871 Python
-    references: 
-    comment: 
-    dimensions(sizes): forecast_time(0), x(101), y(67), pressure(7)
-    variables(dimensions): 
-    groups: 
-
-
-
-
-

When we view our file’s CDL representation now, we can verify that the dimensions were successfully added to the netCDF file:

-
netcdf forecast_model {
-  dimensions:
-    forecast_time = UNLIMITED (currently 13) ;
-    x = 101 ;
-    y = 67 ;
-    pressure = 7 ;
-  attributes:
-    :Conventions = "CF-1.7" ;
-    :title = "Forecast model run" ;
-    :institution = "Unidata" ;
-    :source = "WRF-1.5" ;
-    :history = "2019-07-16 02:21:52.005718 Python" ;
-    :references = "" ;
-    :comment = "" ;
-}
-
-
-
-
-

Creating and filling a variable

-

Thus far, we have only added basic information to this netCDF dataset; namely, the dataset dimensions and some broad metadata. As described briefly above, variables are used to define data fields in netCDF files. Here, we create a netCDF4 variable to hold a data field; in this case, the forecast air temperature. In order to create this netCDF4 variable, we must specify the data type of the values in the data field. We also must specify which dimensions contained in the netCDF file are relevant to this data field. Finally, we can specify whether or not to compress the data using a form of zlib.

-
-
-
temps_var = nc.createVariable(
-    'Temperature',
-    datatype=np.float32,
-    dimensions=('forecast_time', 'pressure', 'y', 'x'),
-    zlib=True,
-)
-
-
-
-
-

We have now created a netCDF4 variable, but it does not yet define a data field. In this example, we use Python to associate our temperature data with the new variable:

-
-
-
temps_var[:] = temps
-temps_var
-
-
-
-
-
<class 'netCDF4._netCDF4.Variable'>
-float32 Temperature(forecast_time, pressure, y, x)
-unlimited dimensions: forecast_time
-current shape = (13, 7, 67, 101)
-filling on, default _FillValue of 9.969209968386869e+36 used
-
-
-
-
-

You can also associate data with a variable sporadically. This example illustrates how to only associate one value per time step with the variable created earlier:

-
-
-
next_slice = 0
-for temp_slice in temps:
-    temps_var[next_slice] = temp_slice
-    next_slice += 1
-
-
-
-
-

At this point, this is the CDL representation of our dataset:

-
netcdf forecast_model {
-  dimensions:
-    forecast_time = UNLIMITED (currently 13) ;
-    x = 101 ;
-    y = 67 ;
-    pressure = 7 ;
-  variables:
-    float Temperature(forecast_time, pressure, y, x) ;
-  attributes:
-    :Conventions = "CF-1.7" ;
-    :title = "Forecast model run" ;
-    :institution = "Unidata" ;
-    :source = "WRF-1.5" ;
-    :history = "2019-07-16 02:21:52.005718 Python" ;
-    :references = "" ;
-    :comment = "" ;
-}
-
-
-

We can also define metadata for this variable in the form of attributes; some specific attributes are required by the CF conventions. For example, the CF conventions require a units attribute to be set for all variables that represent a dimensional quantity. In addition, the value of this attribute must be parsable by the UDUNITS library. In this example, the temperatures are in Kelvin, so we set the units attribute to 'Kelvin'. Next, we set the long_name and standard_name attributes, which are recommended for most datasets, but optional. The long_name attribute contains a longer and more detailed description of a variable. On the other hand, the standard_name attribute names a variable using descriptive words from a predefined word list contained in the CF conventions. Defining these attributes allows users of your datasets to understand what each variable in a dataset represents. Sometimes, data fields do not have valid data values at every dimension point. In this case, the standard is to use a filler value for these missing data values, and to set the missing_value attribute to this filler value. In this case, however, there are no missing values, so the missing_value attribute can be set to any unused value, or not set at all.

-

There are many different sets of recommendations for attributes on netCDF variables. For example, here is NASA’s set of recommended attributes:

-
-

NASA Dataset Interoperability Recommendations:

-

Section 2.2 - Include Basic CF Attributes

-

Include where applicable: units, long_name, standard_name, valid_min / valid_max, scale_factor / add_offset and others.

-
-
-
-
temps_var.units = 'Kelvin'
-temps_var.standard_name = 'air_temperature'
-temps_var.long_name = 'Forecast air temperature'
-temps_var.missing_value = -9999
-temps_var
-
-
-
-
-
<class 'netCDF4._netCDF4.Variable'>
-float32 Temperature(forecast_time, pressure, y, x)
-    units: Kelvin
-    standard_name: air_temperature
-    long_name: Forecast air temperature
-    missing_value: -9999.0
-unlimited dimensions: forecast_time
-current shape = (13, 7, 67, 101)
-filling on, default _FillValue of 9.969209968386869e+36 used
-
-
-
-
-

Here is the variable section of our dataset’s CDL, with the new attributes added:

-
  variables:
-    float Temperature(forecast_time, pressure, y, x) ;
-      Temperature:units = "Kelvin" ;
-      Temperature:standard_name = "air_temperature" ;
-      Temperature:long_name = "Forecast air temperature" ;
-      Temperature:missing_value = -9999.0 ;
-
-
-
-
-

Coordinate variables

-

Dimensions in a netCDF file only define size and alignment metadata. In order to properly orient data in time and space, it is necessary to create “coordinate variables”, which define data values along each dimension. A coordinate variable is typically created as a one-dimensional variable, and has the same name as the corresponding dimension.

-

To start, we define variables which define our x and y coordinate values. It is recommended to include certain attributes for each coordinate variable. First, you should include a standard_name, which allows for associating the variable with projections, among other things. (Projections will be covered in detail later in this page.) Second, you can include an axis attribute, which clearly defines the spatial or temporal direction referred to by the coordinate variable. This next example demonstrates how to set up these attributes:

-
-
-
x_var = nc.createVariable('x', np.float32, ('x',))
-x_var[:] = x
-x_var.units = 'km'
-x_var.axis = 'X'  # Optional
-x_var.standard_name = 'projection_x_coordinate'
-x_var.long_name = 'x-coordinate in projected coordinate system'
-
-y_var = nc.createVariable('y', np.float32, ('y',))
-y_var[:] = y
-y_var.units = 'km'
-y_var.axis = 'Y'  # Optional
-y_var.standard_name = 'projection_y_coordinate'
-y_var.long_name = 'y-coordinate in projected coordinate system'
-
-
-
-
-

Our dataset contains vertical data of air pressure as well, so we must define a coordinate variable for this axis; we can simply call this new variable pressure. Since this axis represents air pressure data, we can set a standard_name of 'air_pressure'. With this standard_name attribute set, it should be obvious to users of this dataset that this variable represents a vertical axis, but for extra clarification, we also set the axis attribute as 'Z'. We can also specify one more attribute, called positive. This attribute indicates whether the variable values increase or decrease as the dimension values increase. Setting this attribute is optional for some data; air pressure is one example. However, we still set the attribute here, for the sake of completeness.

-
-
-
press_var = nc.createVariable('pressure', np.float32, ('pressure',))
-press_var[:] = press
-press_var.units = 'hPa'
-press_var.axis = 'Z'  # Optional
-press_var.standard_name = 'air_pressure'
-press_var.positive = 'down'  # Optional
-
-
-
-
-

Time coordinates must contain a units attribute; this attribute is a string value, and must have a form similar to the string'seconds since 2019-01-06 12:00:00.00'. ‘seconds’, ‘minutes’, ‘hours’, and ‘days’ are the most commonly used time intervals in these strings. It is not recommended to use ‘months’ or ‘years’ in time strings, as the length of these time intervals can vary.

-

Before we can write data, we need to first convert our list of Python datetime objects to numeric values usable in time strings. We can perform this conversion by setting a time string in the format described above, then using the date2num method from the cftime library. An example of this is shown below:

-
-
-
time_units = f'hours since {times[0]:%Y-%m-%d 00:00}'
-time_vals = date2num(times, time_units)
-time_vals
-
-
-
-
-
array([22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34])
-
-
-
-
-

Now that the time string is set up, we have all of the necessary information to set up the attributes for a forecast_time coordinate variable. The creation of this variable is shown in the following example:

-
-
-
time_var = nc.createVariable('forecast_time', np.int32, ('forecast_time',))
-time_var[:] = time_vals
-time_var.units = time_units
-time_var.axis = 'T'  # Optional
-time_var.standard_name = 'time'  # Optional
-time_var.long_name = 'time'
-
-
-
-
-

This next example shows the CDL representation of the netCDF file’s variables at this point. It is clear that much more information is now contained in this representation:

-
  dimensions:
-    forecast_time = UNLIMITED (currently 13) ;
-    x = 101 ;
-    y = 67 ;
-    pressure = 7 ;
-  variables:
-    float x(x) ;
-      x:units = "km" ;
-      x:axis = "X" ;
-      x:standard_name = "projection_x_coordinate" ;
-      x:long_name = "x-coordinate in projected coordinate system" ;
-    float y(y) ;
-      y:units = "km" ;
-      y:axis = "Y" ;
-      y:standard_name = "projection_y_coordinate" ;
-      y:long_name = "y-coordinate in projected coordinate system" ;
-    float pressure(pressure) ;
-      pressure:units = "hPa" ;
-      pressure:axis = "Z" ;
-      pressure:standard_name = "air_pressure" ;
-      pressure:positive = "down" ;
-    float forecast_time(forecast_time) ;
-      forecast_time:units = "hours since 2019-07-16 00:00" ;
-      forecast_time:axis = "T" ;
-      forecast_time:standard_name = "time" ;
-      forecast_time:long_name = "time" ;
-    float Temperature(forecast_time, pressure, y, x) ;
-      Temperature:units = "Kelvin" ;
-      Temperature:standard_name = "air_temperature" ;
-      Temperature:long_name = "Forecast air temperature" ;
-      Temperature:missing_value = -9999.0 ;
-
-
-
-
-

Auxiliary Coordinates

-

Our data are still not CF-compliant, because they do not contain latitude and longitude information, which is needed to properly locate the data. In order to add location data to a netCDF file, we must create so-called “auxiliary coordinate variables” for latitude and longitude. (In this case, the word “auxiliary” means that the variables are not simple one-dimensional variables.)

-

In this next example, we use the Proj function, found in the pyproj library, to create projections of our coordinates. We can then use these projections to generate latitude and longitude values for our data.

-
-
-
X, Y = np.meshgrid(x, y)
-lcc = Proj({'proj': 'lcc', 'lon_0': -105, 'lat_0': 40, 'a': 6371000.0, 'lat_1': 25})
-lon, lat = lcc(X * 1000, Y * 1000, inverse=True)
-
-
-
-
-

Now that we have latitude and longitude values, we can create variables for those values. Both of these variables are two-dimensional; the dimensions in question are y and x. In order to convey that it contains the longitude information, we must set up the longitude variable with a units attribute of 'degrees_east'. In addition, we can provide further clarity by setting a standard_name attribute of 'longitude'. The case is the same for latitude, except the units are 'degrees_north' and the standard_name is 'latitude'.

-
-
-
lon_var = nc.createVariable('lon', np.float64, ('y', 'x'))
-lon_var[:] = lon
-lon_var.units = 'degrees_east'
-lon_var.standard_name = 'longitude'  # Optional
-lon_var.long_name = 'longitude coordinate'
-
-lat_var = nc.createVariable('lat', np.float64, ('y', 'x'))
-lat_var[:] = lat
-lat_var.units = 'degrees_north'
-lat_var.standard_name = 'latitude'  # Optional
-lat_var.long_name = 'latitude coordinate'
-
-
-
-
-

Now that the auxiliary coordinate variables are created, we must identify them as coordinates for the Temperature variable. In order to identify the variables in this way, we set the coordinates attribute of the Temperature variable to a space-separated list of variables to identify, as shown below:

-
-
-
temps_var.coordinates = 'lon lat'
-
-
-
-
-

The portion of the CDL showing the new latitude and longitude variables, as well as the updated Temperature variable, is listed below:

-
  double lon(y, x);
-    lon:units = "degrees_east";
-    lon:long_name = "longitude coordinate";
-    lon:standard_name = "longitude";
-  double lat(y, x);
-    lat:units = "degrees_north";
-    lat:long_name = "latitude coordinate";
-    lat:standard_name = "latitude";
-  float Temperature(time, y, x);
-    Temperature:units = "Kelvin" ;
-    Temperature:standard_name = "air_temperature" ;
-    Temperature:long_name = "Forecast air temperature" ;
-    Temperature:missing_value = -9999.0 ;
-    Temperature:coordinates = "lon lat";
-
-
-
-
-

Coordinate System Information

-

Since the grid containing our data uses a Lambert conformal projection, adding this information to the dataset’s metadata can clear up some possible confusion. We can most easily add this metadata information by making use of a “grid mapping” variable. A grid mapping variable is a “placeholder” variable containing all required grid-mapping information. Other variables that need to access this information can then reference this placeholder variable in their grid_mapping attribute.

-

In this example, we create a grid-mapping variable; this new variable is then set up for a Lambert-conformal conic projection on a spherical globe. By setting this variable’s grid_mapping_name attribute, we can indicate which CF-supported grid mapping this variable refers to. There are additional attributes that can also be set; however, the available options depend on the specific mapping.

-
-
-
proj_var = nc.createVariable('lambert_projection', np.int32, ())
-proj_var.grid_mapping_name = 'lambert_conformal_conic'
-proj_var.standard_parallel = 25.0
-proj_var.latitude_of_projection_origin = 40.0
-proj_var.longitude_of_central_meridian = -105.0
-proj_var.semi_major_axis = 6371000.0
-proj_var
-
-
-
-
-
<class 'netCDF4._netCDF4.Variable'>
-int32 lambert_projection()
-    grid_mapping_name: lambert_conformal_conic
-    standard_parallel: 25.0
-    latitude_of_projection_origin: 40.0
-    longitude_of_central_meridian: -105.0
-    semi_major_axis: 6371000.0
-unlimited dimensions: 
-current shape = ()
-filling on, default _FillValue of -2147483647 used
-
-
-
-
-

Now that we have created a grid-mapping variable, we can specify the grid mapping by setting the grid_mapping attribute to the variable name. In this example, we set the grid_mapping attribute on the Temperature variable:

-
-
-
temps_var.grid_mapping = 'lambert_projection'  # or proj_var.name
-
-
-
-
-

Here is the portion of the CDL containing the modified Temperature variable, as well as the new grid-mapping lambert_projection variable:

-
  variables:
-    int lambert_projection ;
-      lambert_projection:grid_mapping_name = "lambert_conformal_conic ;
-      lambert_projection:standard_parallel = 25.0 ;
-      lambert_projection:latitude_of_projection_origin = 40.0 ;
-      lambert_projection:longitude_of_central_meridian = -105.0 ;
-      lambert_projection:semi_major_axis = 6371000.0 ;
-    float Temperature(forecast_time, pressure, y, x) ;
-      Temperature:units = "Kelvin" ;
-      Temperature:standard_name = "air_temperature" ;
-      Temperature:long_name = "Forecast air temperature" ;
-      Temperature:missing_value = -9999.0 ;
-      Temperature:coordinates = "lon lat" ;
-      Temperature:grid_mapping = "lambert_projection" ;
-
-
-
-
-

Cell Bounds

-

The use of “bounds” attributes is not required, but highly recommended. Here is a relevant excerpt from the NASA Dataset Interoperability Recommendations:

-
-

NASA Dataset Interoperability Recommendations:

-

Section 2.3 - Use CF “bounds” attributes

-

CF conventions state: “When gridded data does not represent the point values of a field but instead represents some characteristic of the field within cells of finite ‘volume,’ a complete description of the variable should include metadata that describes the domain or extent of each cell, and the characteristic of the field that the cell values represent.”

-
-

In this set of examples, consider a rain gauge which is read every three hours, but only dumped every six hours. The netCDF file for this gauge’s data readings might look like this:

-
netcdf precip_bucket_bounds {
-  dimensions:
-      lat = 12 ;
-      lon = 19 ;
-      time = 8 ;
-      tbv = 2;
-  variables:
-      float lat(lat) ;
-      float lon(lon) ;
-      float time(time) ;
-        time:units = "hours since 2019-07-12 00:00:00.00";
-        time:bounds = "time_bounds" ;
-      float time_bounds(time,tbv)
-      float precip(time, lat, lon) ;
-        precip:units = "inches" ;
-  data:
-    time = 3, 6, 9, 12, 15, 18, 21, 24;
-    time_bounds = 0, 3, 0, 6, 6, 9, 6, 12, 12, 15, 12, 18, 18, 21, 18, 24;
-}
-
-
-

Considering the coordinate variable for time, and the bounds attribute set for this variable, the below graph illustrates the times of the gauge’s data readings:

-
|---X
-|-------X
-        |---X
-        |-------X
-                |---X
-                |-------X
-                        |---X
-                        |-------X
-0   3   6   9  12  15  18  21  24
-
-
-

-
-
-
-

Observational Data

-

Thus far, we have only worked with data arranged on grids. One common type of data, called “in-situ” or “observational” data, is usually arranged in other ways. The CF conventions for this type of data are called Conventions for DSG (Discrete Sampling Geometries).

-

For data that are regularly sampled (e.g., from a vertical profiler site), this is straightforward. For these examples, we will be using vertical profile data from three hypothetical profilers, located in Boulder, Norman, and Albany. These hypothetical profilers report data for every 10 m of altitude, from altitudes of 10 m up to (but not including) 1000 m. This first example illustrates how to set up latitude, longitude, altitude, and other necessary data for these profilers:

-
-
-
lons = np.array([-97.1, -105, -73.8])
-lats = np.array([35.25, 40, 42.75])
-heights = np.linspace(10, 1000, 10)
-temps = np.random.randn(lats.size, heights.size)
-stids = ['KBOU', 'KOUN', 'KALB']
-
-
-
-
-
-

Creation and basic setup

-

First, we create a new netCDF file, and define dimensions for it, corresponding to altitude and latitude. Since we are working with observational profile data, we define these dimensions as heights and station. We then set the global featureType attribute to 'profile', which defines the file as holding profile data. In these examples, the term “profile data” is defined as “an ordered set of data points along a vertical line at a fixed horizontal position and fixed time”. In addition, we define a placeholder dimension called str_len, which helps with storing station IDs as strings.

-
-
-
nc.close()
-nc = Dataset('obs_data.nc', 'w', format='NETCDF4_CLASSIC', diskless=True)
-nc.createDimension('station', lats.size)
-nc.createDimension('heights', heights.size)
-nc.createDimension('str_len', 4)
-nc.Conventions = 'CF-1.7'
-nc.featureType = 'profile'
-nc
-
-
-
-
-
<class 'netCDF4._netCDF4.Dataset'>
-root group (NETCDF4_CLASSIC data model, file format HDF5):
-    Conventions: CF-1.7
-    featureType: profile
-    dimensions(sizes): station(3), heights(10), str_len(4)
-    variables(dimensions): 
-    groups: 
-
-
-
-
-

After this initial setup, the current state of our netCDF file is described in the following CDL:

-
netcdf obs_data {
-  dimensions:
-    station = 3 ;
-    heights = 10 ;
-    str_len = 4 ;
-  attributes:
-    :Conventions = "CF-1.7" ;
-    :featureType = "profile" ;
-}
-
-
-

This example illustrates the setup of coordinate variables for latitude and longitude:

-
-
-
lon_var = nc.createVariable('lon', np.float64, ('station',))
-lon_var.units = 'degrees_east'
-lon_var.standard_name = 'longitude'
-
-lat_var = nc.createVariable('lat', np.float64, ('station',))
-lat_var.units = 'degrees_north'
-lat_var.standard_name = 'latitude'
-
-
-
-
-

When a coordinate variable refers to an instance of a feature, netCDF standards refer to it as an “instance variable”. The latitude and longitude coordinate variables declared above are examples of instance variables. In this next example, we create an instance variable for altitude, referred to here as heights:

-
-
-
heights_var = nc.createVariable('heights', np.float32, ('heights',))
-heights_var.units = 'meters'
-heights_var.standard_name = 'altitude'
-heights_var.positive = 'up'
-heights_var[:] = heights
-
-
-
-
-
-
-

Station IDs

-

Using the placeholder dimension defined earlier, we can write the station IDs of our profilers to a variable as well. The variable used to store these station IDs is two-dimensional; however, one of these dimensions only holds metadata designed to aid in converting strings to character arrays. We can also assign the attribute cf_role to this variable, with a value of 'profile_id'. If certain software programs read this netCDF file, this attribute assists in identifying individual profiles.

-
-
-
stid_var = nc.createVariable('stid', 'c', ('station', 'str_len'))
-stid_var.cf_role = 'profile_id'
-stid_var.long_name = 'Station identifier'
-stid_var[:] = stids
-
-
-
-
-

After adding station ID information, our file’s updated CDL should resemble this example:

-
netcdf obs_data {
-  dimensions:
-    station = 3 ;
-    heights = 10 ;
-    str_len = 4 ;
-  variables:
-    double lon(station) ;
-      lon:units = "degrees_east" ;
-      lon:standard_name = "longitude" ;
-    double lat(station) ;
-      lat:units = "degrees_north" ;
-      lat:standard_name = "latitude" ;
-    float heights(heights) ;
-      heights:units = "meters" ;
-      heights:standard_name = "altitude";
-      heights:positive = "up" ;
-    char stid(station, str_len) ;
-      stid:cf_role = "profile_id" ;
-      stid:long_name = "Station identifier" ;
-  attributes:
-    :Conventions = "CF-1.7" ;
-    :featureType = "profile" ;
-}
-
-
-
-
-

Writing the field

-

The final setup step for this netCDF file is to write our actual profile data to the file. In addition, we add an additional scalar variable, which holds the time of data capture for each profile:

-
-
-
time_var = nc.createVariable('time', np.float32, ())
-time_var.units = 'minutes since 2019-07-16 17:00'
-time_var.standard_name = 'time'
-time_var[:] = [5.0]
-
-temp_var = nc.createVariable('temperature', np.float32, ('station', 'heights'))
-temp_var.units = 'celsius'
-temp_var.standard_name = 'air_temperature'
-temp_var.coordinates = 'lon lat heights time'
-
-
-
-
-

The auxiliary coordinate variables in this netCDF file are not proper coordinate variables, and are all associated with the station dimension. Therefore, the names of these variables must be listed in an attribute called coordinates. The final CDL of the variables, including the coordinates attribute, is shown below:

-
  variables:
-    double lon(station) ;
-      lon:units = "degrees_east" ;
-      lon:standard_name = "longitude" ;
-    double lat(station) ;
-      lat:units = "degrees_north" ;
-      lat:standard_name = "latitude" ;
-    float heights(heights) ;
-      heights:units = "meters" ;
-      heights:standard_name = "altitude";
-      heights:positive = "up" ;
-    char stid(station, str_len) ;
-      stid:cf_role = "profile_id" ;
-      stid:long_name = "Station identifier" ;
-    float time ;
-      time:units = "minutes since 2019-07-16 17:00" ;
-      time:standard_name = "time" ;
-    float temperature(station, heights) ;
-      temperature:units = "celsius" ;
-      temperature:standard_name = "air_temperature" ;
-      temperature:coordinates = "lon lat heights time" ;
-
-
-

These standards for storing DSG data in netCDF files can be used for profiler data, as shown in these examples, as well as timeseries and trajectory data, and any combination of these types of data models. You can also use these standards for datasets with differing amounts of data in each feature, using so-called “ragged” arrays. For more information on ragged arrays, or other elements of the CF DSG standards, see the main documentation page, or try some of the annotated DSG examples.

-
-
-
-
-

Summary

-

We have created examples of and discussed the structure of netCDF Datasets, both gridded and in-situ. In addition, we covered the Climate and Forecasting (CF) Conventions, and the setup of netCDF files that follow these conventions. netCDF Datasets are self-describing; in other words, their attributes, or metadata, are included. Other libraries in the Python scientific software ecosystem, such as xarray and MetPy, are therefore easily able to read in, write to, and analyze these Datasets.

-
-

What’s Next?

-

In subsequent notebooks, we will work with netCDF Datasets built from actual, non-example data sources, both model and in-situ.

-

-
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/datetime.html b/_preview/419/core/datetime.html deleted file mode 100644 index 81e466842..000000000 --- a/_preview/419/core/datetime.html +++ /dev/null @@ -1,860 +0,0 @@ - - - - - - - - Datetime — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -
-

Datetime

-
-

Note

-

This content is under construction!

-
-

This section contains tutorials on dealing with times and calendars in scientific Python. The first and most basic of these tutorials covers the standard Python library known as datetime.

-

When this chapter is fully built out, it will include a comprehensive guide to different time libraries, where to use them, and when they might be useful. This set of time libraries includes these libraries, among others:

- -

These tutorials will be cross-referenced with other tutorials on time-related topics, such as dealing with timeseries data in Pandas and Xarray.

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/datetime/datetime.html b/_preview/419/core/datetime/datetime.html deleted file mode 100644 index 8e1b37d4a..000000000 --- a/_preview/419/core/datetime/datetime.html +++ /dev/null @@ -1,1462 +0,0 @@ - - - - - - - - Times and Dates in Python — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -
-

Times and Dates in Python

-
-
-

Overview

-

Time is an essential component of nearly all geoscience data. Timescales commonly used in science can have many different orders of magnitude, from mere microseconds to millions or even billions of years. Some of these magnitudes are listed below:

-
    -
  • microseconds for lightning

  • -
  • hours for a supercell thunderstorm

  • -
  • days for a global weather model

  • -
  • millennia and beyond for the earth’s climate

  • -
-

To properly analyze geoscience data, you must have a firm understanding of how to handle time in Python.

-

In this notebook, we will:

-
    -
  1. Introduce the time and datetime modules from the Python Standard Library

  2. -
  3. Look at formatted input and output of dates and times

  4. -
  5. See how we can do simple arithmetic on date and time data, by making use of the timedelta object

  6. -
  7. Briefly make use of the pytz module to handle some thorny time zone issues in Python.

  8. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Python Quickstart

Necessary

Understanding strings

Basic Python string formatting

Helpful

Try this Real Python string formatting tutorial

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

Imports

-

For the examples on this page, we import three modules from the Python Standard Library, as well as one third-party module. The import syntax used here, as well as a discussion on this syntax and an overview of these modules, can be found in the next section.

-
-
-
# Python Standard Library packages
-# We'll discuss below WHY we alias the packages this way
-import datetime as dt
-import math
-import time as tm
-
-# Third-party package for time zone handling, we'll discuss below!
-import pytz
-
-
-
-
-
-
-

Time Versus Datetime modules

-
-

Some core terminology

-

Every Python installation comes with a Standard Library, which includes many helpful modules; in these examples, we cover the time and datetime modules. Unfortunately, the use of dates and times in Python can be disorienting. There are many different terms used in Python relating to dates and times, and many such terms apply to multiple scopes, such as modules, classes, and functions. For example:

-
    -
  • datetime module has a datetime class

  • -
  • datetime module has a time class

  • -
  • datetime module has a date class

  • -
  • time module has a time function, which returns (almost always) Unix time

  • -
  • datetime class has a date method, which returns a date object

  • -
  • datetime class has a time method, which returns a time object

  • -
-

This confusion can be partially alleviated by aliasing our imported modules, we did above:

-
import datetime as dt
-import time as tm
-
-
-

We can now reference the datetime module (aliased to dt) and datetime class unambiguously.

-
-
-
pisecond = dt.datetime(2021, 3, 14, 15, 9, 26)
-print(pisecond)
-
-
-
-
-
2021-03-14 15:09:26
-
-
-
-
-

Our variable pisecond now stores a particular date and time, which just happens to be \(\pi\)-day 2021 down to the nearest second (3.1415926…).

-
-
-
now = tm.time()
-print(now)
-
-
-
-
-
1691536741.5536904
-
-
-
-
-

The variable now holds the current time in seconds since January 1, 1970 00:00 UTC. For more information on this important, but seemingly esoteric time format, see the section on this page called “What is Unix Time”. In addition, if you are not familiar with UTC, there is a section on this page called “What is UTC”.

-
-
-

time module

-

The time module is well-suited for measuring Unix time. For example, when you are calculating how long it takes a Python function to run, you can employ the time() function, which can be found in the time module, to obtain Unix time before and after the function completes. You can then take the difference of those two times to determine how long the function was running. (Measuring the runtime of a block of code this way is known as “benchmarking”.)

-
-
-
start = tm.time()
-tm.sleep(1)  # The sleep function will stop the program for n seconds
-end = tm.time()
-diff = end - start
-print(f"The benchmark took {diff} seconds")
-
-
-
-
-
The benchmark took 1.00130295753479 seconds
-
-
-
-
-
-

Info

-

You can use the timeit module and the timeit Jupyter magic for more accurate benchmarking. Documentation on these can be found here.

-
-
-
-

What is Unix Time?

-

Unix time is an example of system time, which is how a computer tracks the passage of time. Computers do not inherently know human representations of time; as such, they store time as a large binary number, indicating a number of time units after a set date and time. This is much easier for a computer to keep track of. In the case of Unix time, the time unit is seconds, and the set date and time is the epoch. Therefore, Unix time is the number of seconds since the epoch. The epoch is defined as January 1, 1970 00:00 UTC. This is quite confusing for humans, but again, computers store time in a way that makes sense for them. It is represented “under the hood” as a floating point number which is how computers represent real (ℝ) numbers.

-
-
-

datetime module

-

The datetime module handles time with the Gregorian calendar (the calendar we, as humans, are familiar with); it is independent of Unix time. The datetime module uses an object-oriented approach; it contains the date, time, datetime, timedelta, and tzinfo classes.

-
    -
  • date class represents the day, month, and year

  • -
  • time class represents the time of day

  • -
  • datetime class is a combination of the date and time classes

  • -
  • timedelta class represents a time duration

  • -
  • tzinfo class represents time zones, and is an abstract class.

  • -
-

The datetime module is effective for:

-
    -
  • performing date and time arithmetic and calculating time duration

  • -
  • reading and writing date and time strings with various formats

  • -
  • handling time zones (with the help of third-party libraries)

  • -
-

The time and datetime modules overlap in functionality, but in your geoscientific work, you will probably be using the datetime module more than the time module.

-

We’ll delve into more details below, but here’s a quick example of writing out our pisecond datetime object as a formatted string. Suppose we wanted to write out just the date, and write it in the month/day/year format typically used in the US. We can do this using the strftime() method. This method formats datetime objects using format specifiers. An example of its usage is shown below:

-
-
-
print('Pi day occurred on:', pisecond.strftime(format='%m/%d/%Y'))
-
-
-
-
-
Pi day occurred on: 03/14/2021
-
-
-
-
-
-
-
-

Reading and writing dates and times

-
-

Parsing lightning data timestamps with the datetime.strptime method

-

In this example, we are analyzing US NLDN lightning data. Here is a sample row of data:

-
06/27/07 16:18:21.898 18.739 -88.184 0.0 kA 0 1.0 0.4 2.5 8 1.2 13 G
-
-
-

Part of the task involves parsing the 06/27/07 16:18:21.898 time string into a datetime object. (Although it is outside the scope of this page’s tutorial, a full description of this lightning data format can be found here.) In order to parse this string or others that follow the same format, you will need to employ the datetime.strptime() method from the datetime module. This method takes two arguments:

-
    -
  1. the date/time string you wish to parse

  2. -
  3. the format which describes exactly how the date and time are arranged.

  4. -
-

The full range of formatting options for strftime() and strptime() is described in the Python documentation. In most cases, finding the correct formatting options inherently takes some degree of experimentation to get right. This is a situation where Python shines; you can use the IPython interpreter, or a Jupyter notebook, to quickly test numerous formatting options. Beyond the official documentation, Google and Stack Overflow are your friends in this process.

-

After some trial and error (as described above), you can find that, in this example, the format string '%m/%d/%y %H:%M:%S.%f' will convert the date and time in the data to the correct format.

-
-
-
strike_time = dt.datetime.strptime('06/27/07 16:18:21.898', '%m/%d/%y %H:%M:%S.%f')
-# print strike_time to see if we have properly parsed our time
-print(strike_time)
-
-
-
-
-
2007-06-27 16:18:21.898000
-
-
-
-
-
-
-

Example usage of the datetime object

-

Why did we bother doing this? This is a deceptively simple example; it may appear that we only took the string 06/27/07 16:18:21.898 and reformatted it to 2007-06-27 16:18:21.898000.

-

However, our new variable, strike_time, is in fact a datetime object that we can manipulate in many useful ways.

-

Here are a few quick examples of the advantages of a datetime object:

-
-

Controlling the output format with strftime()

-

The following example shows how to write out the time only, without a date, in a particular format:

-
16h 18m 21s
-
-
-

We can do this with the datetime.strftime() method, which takes a format identical to the one we employed for strptime(). After some trial and error from the IPython interpreter, we arrive at '%Hh %Mm %Ss':

-
-
-
print(strike_time.strftime(format='%Hh %Mm %Ss'))
-
-
-
-
-
16h 18m 21s
-
-
-
-
-
-
-

A simple query of just the year:

-

Here’s a useful shortcut that doesn’t even need a format specifier:

-
-
-
strike_time.year
-
-
-
-
-
2007
-
-
-
-
-

This works because the datetime object stores the data as individual attributes: -year, month, day, hour, minute, second, microsecond.

-
-
-

See how many days have elapsed since the strike:

-

This example shows how to find the number of days since an event; in this case, the lightning strike described earlier:

-
-
-
(dt.datetime.now() - strike_time).days
-
-
-
-
-
5886
-
-
-
-
-

The above example illustrates some simple arithmetic with datetime objects. This commonly-used feature will be covered in more detail in the next section.

-
-
-
-
-

Calculating coastal tides with the timedelta class

-

In these examples, we will look at current data pertaining to coastal tides during a tropical cyclone storm surge.

-

The lunar day is 24 hours and 50 minutes; there are two low tides and two high tides in that time duration. If we know the time of the current high tide, we can easily calculate the occurrence of the next low and high tides by using the timedelta class. (In reality, the exact time of tides is influenced by local coastal effects, in addition to the laws of celestial mechanics, but we will ignore that fact for this exercise.)

-

The timedelta class is initialized by supplying time duration, usually supplied with keyword arguments, to clearly express the length of time. The timedelta class allows you to perform arithmetic with dates and times using standard operators (i.e., +, -, *, /). You can use these operators with a timedelta object, and either another timedelta object, a datetime object, or a numeric literal, to obtain objects representing new dates and times.

-

This convenient language feature is known as operator overloading, and is another example of Python offering built-in functionality to make programming easier. (In some other languages, such as Java, you would have to call a method to perform such operations, which significantly obfuscates the code.)

-

In addition, you can use these arithmetic operators with two datetime objects, as shown above with lightning-strike data, to create timedelta objects. Let’s examine all these features in the following code block.

-
-
-
high_tide = dt.datetime(2016, 6, 1, 4, 38, 0)
-lunar_day = dt.timedelta(hours=24, minutes=50)
-tide_duration = lunar_day / 4  # Here we do some arithmetic on the timedelta object!
-next_low_tide = (
-    high_tide + tide_duration
-)  # Here we add a timedelta object to a datetime object
-next_high_tide = high_tide + (2 * tide_duration)  # and so on
-tide_length = next_high_tide - high_tide
-print(f"The time between high and low tide is {tide_duration}.")
-print(f"The current high tide is {high_tide}.")
-print(f"The next low tide is {next_low_tide}.")
-print(f"The next high tide {next_high_tide}.")
-print(f"The tide length is {tide_length}.")
-print(f"The type of the 'tide_length' variable is {type(tide_length)}.")
-
-
-
-
-
The time between high and low tide is 6:12:30.
-The current high tide is 2016-06-01 04:38:00.
-The next low tide is 2016-06-01 10:50:30.
-The next high tide 2016-06-01 17:03:00.
-The tide length is 12:25:00.
-The type of the 'tide_length' variable is <class 'datetime.timedelta'>.
-
-
-
-
-

To illustrate that the difference of two times yields a timedelta object, we can use a built-in Python function called type(), which returns the type of its argument. In the above example, we call type() in the last print statement, and it returns the type of timedelta.

-
-
-

Dealing with Time Zones

-

Time zones can be a source of confusion and frustration in geoscientific data and in computer programming in general. Core date and time libraries in various programming languages, including Python, inevitably have design flaws, relating to time zones, date and time formatting, and other inherently complex issues. Third-party libraries are often created to fix the limitations of the core libraries, but this approach is frequently unsuccessful. To avoid time-zone-related issues, it is best to handle data in UTC; if data cannot be handled in UTC, efforts should be made to consistently use the same time zone for all data. However, this is not always possible; events such as severe weather are expected to be reported in a local time zone, which is not always consistent.

-
-

What is UTC?

-

UTC” is a combination of the French and English abbreviations for Coordinated Universal Time. It is, in practice, equivalent to Greenwich Mean Time (GMT), the time zone at 0 degrees longitude. (The prime meridian, 0 degrees longitude, runs through Greenwich, a district of London, England.) In geoscientific data, times are often in UTC, although you should always verify that this is actually true to avoid time zone issues.

-
-
-

Time Zone Naive Versus Time Zone Aware datetime Objects

-

When you create datetime objects in Python, they are “time zone naive”, or, if the subject of time zones is assumed, simply “naive”. This means that they are unaware of the time zone of the date and time they represent; time zone naive is the opposite of time zone aware. In many situations, you can happily go forward without this detail getting in the way of your work. As the Python documentation states:

-
-

Naive objects are easy to understand and to work with, at the cost of ignoring some aspects of reality.

-
-

However, if you wish to convey time zone information, you will have to make your datetime objects time zone aware. The datetime library is able to easily convert the time zone to UTC, also converting the object to a time zone aware state, as shown below:

-
-
-
naive = dt.datetime.now()
-aware = dt.datetime.now(dt.timezone.utc)
-print(f"I am time zone naive {naive}.")
-print(f"I am time zone aware {aware}.")
-
-
-
-
-
I am time zone naive 2023-08-08 23:19:02.623223.
-I am time zone aware 2023-08-08 23:19:02.623259+00:00.
-
-
-
-
-

Notice that aware has +00:00 appended at the end, indicating zero hours offset from UTC.

-

Our naive object shows the local time on whatever computer was used to run this code. If you’re reading this online, chances are the code was executed on a cloud server that already uses UTC. If this is the case, naive and aware will differ only at the microsecond level, due to round-off error.

-

In the code above, we used dt.timezone.utc to initialize the UTC timezone for our aware object. Unfortunately, at this time, the Python Standard Library does not fully support initializing datetime objects with arbitrary time zones; it also does not fully support conversions between time zones for datetime objects. However, there exist third-party libraries that provide some of this functionality; one such library is covered below.

-
-
-

Full time zone support with the pytz module

-

For improved handling of time zones in Python, you will need the third-party pytz module, whose classes build upon, or, in object-oriented programming terms, inherit from, classes from the datetime module.

-

In this next example, we repeat the above exercise, but this time, we use a method from the pytz module to initialize the aware object in a different time zone:

-
-
-
naive = dt.datetime.now()
-aware = dt.datetime.now(pytz.timezone('US/Mountain'))
-print(f"I am time zone naive: {naive}.")
-print(f"I am time zone aware: {aware}.")
-
-
-
-
-
I am time zone naive: 2023-08-08 23:19:02.631378.
-I am time zone aware: 2023-08-08 17:19:02.646565-06:00.
-
-
-
-
-

The pytz.timezone() method takes a time zone string; if this string is formatted correctly, the method returns a tzinfo object, which can be used when making a datetime object time zone aware. This initializes the time zone for the newly aware object to a specific time zone matching the time zone string. The -06:00 indicates that we are now operating in a time zone six hours behind UTC.

-
- -
-
-
-

Summary

-

The Python Standard Library contains several modules for dealing with date and time data. We saw how we can avoid some name ambiguities by aliasing the module names; this can be done with import statements like import datetime as dt and import time as tm. The tm.time() method just returns the current Unix time in seconds – which can be useful for measuring elapsed time, but not all that useful for working with geophysical data.

-

The datetime module contains various classes for storing, converting, comparing, and formatting date and time data on the Gregorian calendar. We saw how we can parse data files with date and time strings into dt.datetime objects using the dt.datetime.strptime() method. We also saw how to perform arithmetic using date and time data; this uses the dt.timedelta class to represent intervals of time.

-

Finally, we looked at using the third-party pytz module to handle time zone awareness and conversions.

-
-

What’s Next?

-

In subsequent tutorials, we will dig deeper into different time and date formats, and discuss how they are handled by important Python modules such as Numpy, Pandas, and Xarray.

-
-
-
-

Resources and References

-

This page was based on and adapted from material in Unidata’s Python Training.

-

For further reading on these modules, take a look at the official documentation for:

- -

For more information on Python string formatting, try:

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/matplotlib.html b/_preview/419/core/matplotlib.html deleted file mode 100644 index ffc34705b..000000000 --- a/_preview/419/core/matplotlib.html +++ /dev/null @@ -1,890 +0,0 @@ - - - - - - - - Matplotlib — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
- On this page -
- -
-
-
-
-
- -
- -

Matplotlib logo

-
-

Matplotlib

-

Matplotlib is the go-to library for plotting within Python. Numerous packages and libraries build off of Matplotlib, making it the de facto standard Python plotting package. If you were to learn a single plotting tool to keep in your toolbox, this is it.

-
-

Why Matplotlib?

-

Matplotlib is a plotting library for Python and is often the first plotting package Python learners encounter. You may be wondering, “Why learn Matplotlib? Why not Seaborn or another plotting library first?”

-

The simple answer to the much-asked question of “why Matplotlib?” is that it is extremely popular; in fact, Matplotlib is one of the most popular Python packages. Because of its history as Python’s “go-to” plotting package, most other open source plotting libraries, including Seaborn, are built on top of Matplotlib; thus, these more specialized plotting packages inherit some of Matplotlib’s capabilities, syntax, and limitations. Thus, you will find it useful to be familiar with Matplotlib when learning other plotting libraries.

-

Matplotlib supports a variety of output formats, chart types, and interactive options, and runs well on most operating systems and graphic backends. The key features of Matplotlib are its extensibility and the extensive documentation available to the community. All of these things contribute to Matplotlib’s popularity, which is the answer to the question of “Why Matplotlib”, and the reason Matplotlib is the first plotting package we will introduce you to in this book.

-
-
-

In this section

-

In this section of Pythia Foundations, you will find tutorials on basic plotting with Matplotlib.

-

From the Matplotlib documentation, “Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python.”

-

Currently, Pythia Foundations provides a basic introduction to Matplotlib, as well as:

-
    -
  • Histograms

  • -
  • Piecharts

  • -
  • Animations

  • -
  • Annotations

  • -
  • Colorbars

  • -
  • Contour plots

  • -
  • Customizing layouts

  • -
-
-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/matplotlib/annotations-colorbars-layouts.html b/_preview/419/core/matplotlib/annotations-colorbars-layouts.html deleted file mode 100644 index 82975b721..000000000 --- a/_preview/419/core/matplotlib/annotations-colorbars-layouts.html +++ /dev/null @@ -1,1440 +0,0 @@ - - - - - - - - Annotations, Colorbars, and Advanced Layouts — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -

Matplotlib logo

-
-

Annotations, Colorbars, and Advanced Layouts

-
-
-

Overview

-

In this section we explore methods for customizing plots. The following topics will be covered:

-
    -
  1. Adding annotations

  2. -
  3. Rendering equations

  4. -
  5. Colormap overview

  6. -
  7. Basic colorbars

  8. -
  9. Shared colorbars

  10. -
  11. Custom colorbars

  12. -
  13. Mosaic subplots

  14. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - -

Concepts

Importance

NumPy Basics

Necessary

Matplotlib Basics

Necessary

-
    -
  • Time to learn: 30-40 minutes

  • -
-
-
-

Imports

-

Here, we import the matplotlib.pyplot interface and numpy, in addition to the scipy statistics package (scipy.stats) for generating sample data.

-
-
-
import matplotlib.pyplot as plt
-import numpy as np
-import scipy.stats as stats
-from matplotlib.colors import LinearSegmentedColormap, ListedColormap, Normalize
-
-
-
-
-
-
-

Create Some Sample Data

-

By using scipy.stats, the Scipy statistics package described above, we can easily create a data array containing a normal distribution. We can plot these data points to confirm that the correct distribution was generated. The generated sample data will then be used later in this section. The code and sample plot for this data generation are as follows:

-
-
-
mu = 0
-variance = 1
-sigma = np.sqrt(variance)
-
-x = np.linspace(mu - 3 * sigma, mu + 3 * sigma, 200)
-pdf = stats.norm.pdf(x, mu, sigma)
-
-plt.plot(x, pdf);
-
-
-
-
-../../_images/e50e5648082ce22b52011d302a7cda36b0348198e192885034e1b4509ffbae6c.png -
-
-
-
-

Adding Annotations

-

A common part of many people’s workflows is adding annotations. A rough definition of ‘annotation’ is ‘a note of explanation or comment added to text or a diagram’.

-

We can add an annotation to a plot using plt.text. This method takes the x and y data coordinates at which to draw the annotation (as floating-point values), and the string containing the annotation text.

-
-
-
plt.plot(x, pdf)
-plt.text(0, 0.05, 'here is some text!');
-
-
-
-
-../../_images/f19db708fb5b9c4ba49d3d6dafb3274fe7de98450ed19dd44c22fc4019971c8e.png -
-
-
-
-

Rendering Equations

-

We can also add annotations with equation formatting, by using LaTeX syntax. The key is to use strings in the following format:

-
r'$some_equation$'
-
-
-

Let’s run an example that renders the following equation as an annotation:

-
-\[f(x) = \frac{1}{\mu\sqrt{2\pi}} e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2}\]
-

The next code block and plot demonstrate rendering this equation as an annotation.

-

If you are interested in learning more about LaTeX syntax, check out their official documentation.

-

Furthermore, if the code is being executed in a Jupyter notebook run interactively (e.g., on Binder), you can double-click on the cell to see the LaTeX source for the rendered equation.

-
-
-
plt.plot(x, pdf)
-
-plt.text(
-    -1,
-    0.05,
-    r'$f(x) = \frac{1}{\mu\sqrt{2\pi}}  e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2}$',
-);
-
-
-
-
-../../_images/88b2bbd209d3812d32446e04ae2bc7ec6a9d0c9d76c64a8f56ad8304f51dd313.png -
-
-

As you can see, the equation was correctly rendered in the plot above. However, the equation appears quite small. We can increase the size of the text using the fontsize keyword argument, and center the equation using the ha (horizontal alignment) keyword argument.

-

The following example illustrates the use of these keyword arguments, as well as creating a legend containing LaTeX notation:

-
-
-
fstr = r'$f(x) = \frac{1}{\mu\sqrt{2\pi}}  e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2}$'
-
-plt.plot(x, pdf, label=r'$\mu=0, \,\, \sigma^2 = 1$')
-plt.text(0, 0.05, fstr, fontsize=15, ha='center')
-plt.legend();
-
-
-
-
-../../_images/accf7e6cbbf7180ed9f864595a0be49ef97ebb1d94b62dbbe885cd9711bb5d6c.png -
-
-
-

Add a Box Around the Text

-

To improve readability, we can also add a box around the equation text. This is done using bbox.

-

bbox is a keyword argument in plt.text that creates a box around text. It takes a dictionary that specifies options, behaving like additional keyword arguments inside of the bbox argument. In this case, we use the following dictionary keys:

-
    -
  • a rounded box style (boxstyle = 'round')

  • -
  • a light grey facecolor (fc = 'lightgrey')

  • -
  • a black edgecolor (ec = 'k')

  • -
-

This example demonstrates the correct use of bbox:

-
-
-
fig = plt.figure(figsize=(10, 8))
-plt.plot(x, pdf)
-
-fstr = r'$f(x) = \frac{1}{\mu\sqrt{2\pi}}  e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2}$'
-plt.text(
-    0,
-    0.05,
-    fstr,
-    fontsize=18,
-    ha='center',
-    bbox=dict(boxstyle='round', fc='lightgrey', ec='k'),
-)
-
-plt.xticks(fontsize=16)
-plt.yticks(fontsize=16)
-
-plt.title("Normal Distribution with SciPy", fontsize=24);
-
-
-
-
-../../_images/f88ff83440607b4ed1d0eb0e919080ab0c192b8feadd08b2ee6bced042228bcc.png -
-
-
-
-
-
-

Colormap Overview

-

Colormaps are a visually appealing method of looking at visualized data in a new and different way. They associate specific values with hues, using color to ease rapid understanding of plotted data; for example, displaying hotter temperatures as red and colder temperatures as blue.

-
-

Classes of colormaps

-

There are four different classes of colormaps, and many individual maps are contained in each class. To view some examples for each class, use the dropdown arrow next to the class name below.

-
- 1. Sequential: These colormaps incrementally increase or decrease in lightness and/or saturation of color. In general, they work best for ordered data. -

Perceptually Sequential

-

Sequential

-

Sequential2

-

Perceptually Sequential

-

Sequential

-

Sequential2

-
-
- 2. Diverging: These colormaps contain two colors that change in lightness and/or saturation in proportion to distance from the middle, and an unsaturated color in the middle. They are almost always used with data containing a natural zero point, such as sea level. -

Diverging

-

Diverging

-
-
- 3. Cyclic: These colormaps have two different colors that change in lightness and meet in the middle, and unsaturated colors at the beginning and end. They are usually best for data values that wrap around, such as longitude. -

Cyclic

-

Cyclic

-
-
- 4. Qualitative: These colormaps have no pattern, and are mostly bands of miscellaneous colors. You should only use these colormaps for unordered data without relationships. -

Qualitative

-

Miscellanous

-

Miscellanous

-
-
-

Other considerations

-

There is a lot of info about choosing colormaps that could be its own tutorial. Two important considerations:

-
    -
  1. Color-blind friendly patterns: By using colormaps that do not contain both red and green, you can help people with the most common form of color blindness read your data plots more easily. The GeoCAT examples gallery has a section about picking better colormaps that covers this issue in greater detail.

  2. -
  3. Grayscale conversion: It is not too uncommon for a plot originally rendered in color to be converted to black-and-white (monochrome grayscale). This reduces the usefulness of specific colormaps, as shown below.

  4. -
-

hsv colormap in grayscale

- -
-
-
-

Basic Colorbars

-

Before we look at a colorbar, let’s generate some fake X and Y data using numpy.random, and set a number of bins for a histogram:

-
-
-
npts = 1000
-nbins = 15
-
-x = np.random.normal(size=npts)
-y = np.random.normal(size=npts)
-
-
-
-
-

Now we can use our fake data to plot a 2-D histogram with the number of bins set above. We then add a colorbar to the plot, using the default colormap viridis.

-
-
-
fig = plt.figure()
-ax = plt.gca()
-
-plt.hist2d(x, y, bins=nbins, density=True)
-plt.colorbar();
-
-
-
-
-../../_images/1b035ecaa9cd12c78098c6e73d3083bf3392010eb657fc0f6d9faeaee7f63bf4.png -
-
-

We can change which colormap to use by setting the keyword argument cmap = 'colormap_name' in the plotting function call. This sets the colormap not only for the plot, but for the colorbar as well. In this case, we use the magma colormap:

-
-
-
fig = plt.figure()
-ax = plt.gca()
-
-plt.hist2d(x, y, bins=nbins, density=True, cmap='magma')
-plt.colorbar();
-
-
-
-
-../../_images/c016b1383793847705244cc4b8f1129f861e794226d30aebbaaa7556acab6487.png -
-
-
-
-

Shared Colorbars

-

Oftentimes, you are plotting multiple subplots, or multiple Axes objects, simultaneously. In these scenarios, you can create colorbars that span multiple plots, as shown in the following example:

-
-
-
fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)
-
-hist1 = ax[0].hist2d(x, y, bins=15, density=True, vmax=0.18)
-hist2 = ax[1].hist2d(x, y, bins=30, density=True, vmax=0.18)
-
-fig.colorbar(hist1[3], ax=ax, location='bottom')
-
-
-
-
-
<matplotlib.colorbar.Colorbar at 0x7f467ee4e110>
-
-
-../../_images/c481476eb1c4e8f74b4334133f26d23b9bb4fcb58fb6cd3527bf277318b10155.png -
-
-

You may be wondering why the call to fig.colorbar uses the argument hist1[3]. The explanation is as follows: hist1 is a tuple returned by hist2d, and hist1[3] contains a matplotlib.collections.QuadMesh that points to the colormap for the first histogram. To make sure that both histograms are using the same colormap with the same range of values, vmax is set to 0.18 for both plots. This ensures that both histograms are using colormaps that represent values from 0 (the default for histograms) to 0.18. Because the same data values are used for both plots, it doesn’t matter whether we pass in hist1[3] or hist2[3] to fig.colorbar. -You can learn more about this topic by reviewing the matplotlib.axes.Axes.hist2d documentation.

-

In addition, there are many other types of plots that can also share colorbars. An actual use case that is quite common is to use shared colorbars to compare data between filled contour plots. The vmin and vmax keyword arguments behave the same way for contourf as they do for hist2d. However, there is a potential downside to using the vmin and vmax kwargs. When plotting two different datasets, the dataset with the smaller range of values won’t show the full range of colors, even though the colormaps are the same. Thus, it can potentially matter which output from contourf is used to make a colorbar. The following examples demonstrate general plotting technique for filled contour plots with shared colorbars, as well as best practices for dealing with some of these logistical issues:

-
-
-
x2 = y2 = np.arange(-3, 3.01, 0.025)
-X2, Y2 = np.meshgrid(x2, y2)
-Z = np.sqrt(np.sin(X2) ** 2 + np.sin(Y2) ** 2)
-Z2 = np.sqrt(2 * np.cos(X2) ** 2 + 2 * np.cos(Y2) ** 2)
-
-fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)
-c1 = ax[0].contourf(X2, Y2, Z, vmin=0, vmax=2)
-c2 = ax[1].contourf(X2, Y2, Z2, vmin=0, vmax=2)
-fig.colorbar(c1, ax=ax[0], location='bottom')
-fig.colorbar(c2, ax=ax[1], location='bottom')
-
-fig.suptitle('Shared colormaps on data with different ranges')
-
-
-
-
-
Text(0.5, 0.98, 'Shared colormaps on data with different ranges')
-
-
-../../_images/228ac1dc298116eb71ac8a7fbab76bffdb48ce6b78779bcc2c31837a01fcd6b5.png -
-
-
-
-
fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)
-c1 = ax[0].contourf(X2, Y2, Z, vmin=0, vmax=2)
-c2 = ax[1].contourf(X2, Y2, Z2, vmin=0, vmax=2)
-fig.colorbar(c2, ax=ax, location='bottom')
-
-fig.suptitle('Using the contourf output from the data with a wider range')
-
-
-
-
-
Text(0.5, 0.98, 'Using the contourf output from the data with a wider range')
-
-
-../../_images/50ac6e41733165de86b0c10d10b1ea9caa25a288943ce8cc785d8ffdc654eac7.png -
-
-
-
-

Custom Colorbars

-

Despite the availability of a large number of premade colorbar styles, it can still occasionally be helpful to create your own colorbars.

-

Below are 2 similar examples of using custom colorbars.

-

The first example uses a very discrete list of colors, simply named colors, and creates a colormap from this list by using the call ListedColormap.

-

The second example uses the function LinearSegmentedColormap to create a new colormap, using interpolation and the colors list defined in the first example.

-
-
-
colors = [
-    'white',
-    'pink',
-    'red',
-    'orange',
-    'yellow',
-    'green',
-    'blue',
-    'purple',
-    'black',
-]
-ccmap = ListedColormap(colors)
-norm = Normalize(vmin=0, vmax=0.18)
-
-fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)
-
-hist1 = ax[0].hist2d(x, y, bins=15, density=True, cmap=ccmap, norm=norm)
-hist2 = ax[1].hist2d(x, y, bins=30, density=True, cmap=ccmap, norm=norm)
-
-cbar = fig.colorbar(hist1[3], ax=ax, location='bottom')
-
-
-
-
-../../_images/079756d879cd417fc7801df083ca25cd2f399a4e449609b9a4d4dca2f818d896.png -
-
-
-
-
cbcmap = LinearSegmentedColormap.from_list("cbcmap", colors)
-
-fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True)
-
-hist1 = ax[0].hist2d(x, y, bins=15, density=True, cmap=cbcmap, norm=norm)
-hist2 = ax[1].hist2d(x, y, bins=30, density=True, cmap=cbcmap, norm=norm)
-
-cbar = fig.colorbar(hist1[3], ax=ax, location='bottom')
-
-
-
-
-../../_images/3446d7febb92b54b0dfcce822462ede035bf572bce32c02b92e458d9fc33958e.png -
-
-
-

The Normalize Class

-

Notice that both of these examples contain plotting functions that make use of the norm kwarg. This keyword argument takes an object of the Normalize class. A Normalize object is constructed with two numeric values, representing the start and end of the data. It then linearly normalizes the data in that range into an interval of [0,1]. If this sounds familiar, it is because this functionality was used in a previous histogram example. Feel free to review any previous examples if you need a refresher on particular topics. In this example, the values of the vmin and vmax kwargs used in hist2d are reused as arguments to the Normalize class constructor. This sets the values of vmin and vmax as the starting and ending data values for our Normalize object, which is passed to the norm kwarg of hist2d to normalize the data. There are many different options for normalizing data, and it is important to explicitly specify how you want your data normalized, especially when making a custom colormap.

-

For information on nonlinear and other complex forms of normalization, review this Colormap Normalization tutorial.

-
-
-
-

Mosaic Subplots

-

One of the helpful features recently added to Matplotlib is the subplot_mosaic method. This method allows you to specify the structure of your figure using specially formatted strings, and will generate subplots automatically based on that structure.

-

For example, if we wanted two plots on top, and one on the bottom, we can construct them by passing the following string to subplot_mosaic:

-
""
-AB
-CC
-""
-
-
-

This creates three Axes objects corresponding to three subplots. The subplots A and B are on top of the subplot C, and the C subplot spans the combined width of A and B.

-

Once we create the subplots, we can access them using the dictionary returned by subplot_mosaic. You can specify an Axes object (in this example, your_axis) in the dictionary (in this example, axes_dict) by using the syntax axes_dict['your_axis']. A full example of subplot_mosaic is as follows:

-
-
-
axdict = plt.figure(constrained_layout=True).subplot_mosaic(
-    """
-    AB
-    CC
-    """
-)
-
-histA = axdict['A'].hist2d(x, y, bins=15, density=True, cmap=cbcmap, norm=norm)
-histB = axdict['B'].hist2d(x, y, bins=10, density=True, cmap=cbcmap, norm=norm)
-histC = axdict['C'].hist2d(x, y, bins=30, density=True, cmap=cbcmap, norm=norm)
-
-
-
-
-../../_images/0a92ae20d4d956fd7e5b402e961247f0b1a8ec92a6e0a85ab2bb61a51765799b.png -
-
-

You’ll notice there is not a colorbar plotted by default. When constructing the colorbar, we need to specify the following:

-
    -
  • Which plot to use for the colormapping (ex. histA)

  • -
  • Which subplots (Axes objects) to merge colorbars across (ex. [histA, histB])

  • -
  • Where to place the colorbar (ex. bottom)

  • -
-
-
-
axdict = plt.figure(constrained_layout=True).subplot_mosaic(
-    """
-    AB
-    CC
-    """
-)
-
-histA = axdict['A'].hist2d(x, y, bins=15, density=True, cmap=cbcmap, norm=norm)
-histB = axdict['B'].hist2d(x, y, bins=10, density=True, cmap=cbcmap, norm=norm)
-histC = axdict['C'].hist2d(x, y, bins=30, density=True, cmap=cbcmap, norm=norm)
-
-fig.colorbar(histA[3], ax=[axdict['A'], axdict['B']], location='bottom')
-fig.colorbar(histC[3], ax=[axdict['C']], location='right');
-
-
-
-
-../../_images/a526b1544f5a70f1d8ea9a6cebc23bf7da88a5425d98c9ad58be733784bd8ccc.png -
-
-
-
-
-

Summary

-
    -
  • You can use features in Matplotlib to add text annotations to your plots, including equations in mathematical notation

  • -
  • There are a number of considerations to take into account when choosing your colormap

  • -
  • You can create your own colormaps with Matplotlib

  • -
  • Various subplots and corresponding Axes objects in a figure can share colorbars

  • -
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/matplotlib/histograms-piecharts-animation.html b/_preview/419/core/matplotlib/histograms-piecharts-animation.html deleted file mode 100644 index f2eeb143d..000000000 --- a/_preview/419/core/matplotlib/histograms-piecharts-animation.html +++ /dev/null @@ -1,3761 +0,0 @@ - - - - - - - - Histograms, Pie Charts, and Animations — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -

Matplotlib logo

-
-

Histograms, Pie Charts, and Animations

-
-
-

Overview

-

In this section we’ll explore some more specialized plot types, including:

-
    -
  1. Histograms

  2. -
  3. Pie Charts

  4. -
  5. Animations

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

NumPy Basics

Necessary

Matplotlib Basics

Necessary

-
    -
  • Time to Learn: 30 minutes

  • -
-
-
-
-

Imports

-

Just like in the previous tutorial, we are going to import Matplotlib’s pyplot interface as plt. We must also import numpy for working with data arrays.

-
-
-
import matplotlib.pyplot as plt
-import numpy as np
-
-
-
-
-
-
-

Histograms

-

We can plot a 1-D histogram using most 1-D data arrays.

-

To get the 1-D data array for this example, we generate example data using NumPy’s normal-distribution random-number generator. For demonstration purposes, we’ve specified the random seed for reproducibility. The code for this number generation is as follows:

-
-
-
npts = 2500
-nbins = 15
-
-np.random.seed(0)
-x = np.random.normal(size=npts)
-
-
-
-
-

Now that we have our data array, we can make a histogram using plt.hist. In this case, we change the y-axis to represent probability, instead of count; this is performed by setting density=True.

-
-
-
plt.hist(x, bins=nbins, density=True)
-plt.title('1D histogram')
-plt.xlabel('Data')
-plt.ylabel('Probability');
-
-
-
-
-../../_images/63cb5fc6111129f02f6f253c4f2d36aa03af181761194e9039182c1cc7fc528f.png -
-
-

Similarly, we can make a 2-D histogram, by first generating a second 1-D array, and then calling plt.hist2d with both 1-D arrays as arguments:

-
-
-
y = np.random.normal(size=npts)
-
-plt.hist2d(x, y, bins=nbins);
-
-
-
-
-../../_images/afaa1ad3f96880a24585ffffedda5c8e155befe28121c2d6674d7c6e778f867c.png -
-
-
-
-

Pie Charts

-

Matplotlib also has the capability to plot pie charts, by way of plt.pie. The most basic implementation uses a 1-D array of wedge ‘sizes’ (i.e., percent values), as shown below:

-
-
-
x = np.array([25, 15, 20, 40])
-plt.pie(x);
-
-
-
-
-../../_images/46ad4cc89c24786225e70dc43a5be6f1b6b4fd6a621e4f4da024f3db05409dd5.png -
-
-

Typically, you’ll see examples where all of the values in the array x will sum to 100, but the data values provided to plt.pie do not necessarily have to add up to 100. The sum of the numbers provided will be normalized to 1, and the individual values will thereby be converted to percentages, regardless of the actual sum of the values. If this behavior is unwanted or unneeded, you can set normalize=False.

-

If you set normalize=False, and the sum of the values of x is less than 1, then a partial pie chart is plotted. If the values sum to larger than 1, a ValueError will be raised.

-
-
-
x = np.array([0.25, 0.20, 0.40])
-plt.pie(x, normalize=False);
-
-
-
-
-../../_images/e788d89759ef499556b8bd4e439e792ade760fd064e6bf130a341e772ee38cd5.png -
-
-

Let’s do a more complicated example.

-

Here we create a pie chart with various sizes associated with each color. Labels are derived by capitalizing each color in the array colors. Since colors can be specified by strings corresponding to named colors, this allows both the colors and the labels to be set from the same array, reducing code and effort.

-

If you want to offset one or more wedges for effect, you can use the explode keyword argument. The value for this argument must be a list of floating-point numbers with the same length as the number of wedges. The numbers indicate the percentage of offset for each wedge. In this example, each wedge is not offset except for the pink (3rd index).

-
-
-
colors = ['red', 'blue', 'yellow', 'pink', 'green']
-labels = [c.capitalize() for c in colors]
-
-sizes = [1, 3, 5, 7, 9]
-explode = (0, 0, 0, 0.1, 0)
-
-
-plt.pie(sizes, labels=labels, explode=explode, colors=colors, autopct='%1.1f%%');
-
-
-
-
-../../_images/b4b780f606ceda586490a9cc3371de2a6bb0d63382deff50658a6d6e6624dea0.png -
-
-
-
-

Animations

-

Matplotlib offers a single commonly-used animation tool, FuncAnimation. This tool must be imported separately through Matplotlib’s animation package, as shown below. You can find more information on animation with Matplotlib at the official documentation page.

-
-
-
from matplotlib.animation import FuncAnimation
-
-
-
-
-

FuncAnimation creates animations by repeatedly calling a function. Using this method involves three main steps:

-
    -
  1. Create an initial state of the plot

  2. -
  3. Make a function that can “progress” the plot to the next frame of the animation

  4. -
  5. Create the animation using FuncAnimation

  6. -
-

For this example, let’s create an animated sine wave.

-
-

Step 1: Initial State

-

In the initial state step, we will define a function called init. This function will then create the animation plot in its initial state. However, please note that the successful use of FuncAnimation does not technically require such a function; in a later example, creating animations without an initial-state function is demonstrated.

-

First, we’ll define Figure and Axes objects. After that, we can create a line-plot object (referred to here as a line) with plt.plot. To create the initialization function, we set the line’s data to be empty and then return the line.

-

Please note, this code block will display a blank plot when run as a Jupyter notebook cell.

-
-
-
fig, ax = plt.subplots()
-ax.set_xlim(0, 2 * np.pi)
-ax.set_ylim(-1.5, 1.5)
-
-(line,) = ax.plot([], [])
-
-
-def init():
-    line.set_data([], [])
-    return (line,)
-
-
-
-
-../../_images/62ced909485cf68ae457c1551f4583f370bd3fc3eb78b98e3e656f1595b2a36d.png -
-
-
-
-

Step 2: Animation Progression Function

-

For this step, we create a progression function, which takes an index (usually named n or i), and returns the corresponding (in other words, n-th or i-th) frame of the animation.

-
-
-
def animate(i):
-    x = np.linspace(0, 2 * np.pi, 250)
-
-    y = np.sin(2 * np.pi * (x - 0.1 * i))
-
-    line.set_data(x, y)
-
-    return (line,)
-
-
-
-
-
-
-

Step 3: Using FuncAnimation

-

The last step is to feed the parts we created to FuncAnimation. Please note, when using the FuncAnimation function, it is important to save the output in a variable, even if you do not intend to use this output later. If you do not, Python’s garbage collector may attempt to save memory by deleting the animation data, and it will be unavailable for later use.

-
-
-
anim = FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)
-
-
-
-
-

In order to show the animation in a Jupyter notebook, we have to use the rc function. This function must be imported separately, and is used to set specific parameters in Matplotlib. In this case, we need to set the html parameter for animation plots to html5, instead of the default value of none. The code for this is written as follows:

-
-
-
from matplotlib import rc
-
-rc('animation', html='html5')
-
-anim
-
-
-
-
-
-
-
-
-

Saving an Animation

-

To save an animation to a file, use the save() method of the animation variable, in this case anim.save(), as shown below. The arguments are the file name to save the animation to, in this case animate.gif, and the writer used to save the file. Here, the animation writer chosen is Pillow, a library for image processing in Python. There are many choices for an animation writer, which are described in detail in the Matplotlib writer documentation. The documentation for the Pillow writer is described on this page; links to other writer documentation pages are on the left side of the Pillow writer documentation.

-
-
-
anim.save('animate.gif', writer='pillow');
-
-
-
-
-
-
-
-
-

Summary

-
    -
  • Matplotlib supports many different plot types, including the less-commonly-used types described in this section.

  • -
  • Some of these lesser-used plot types include histograms and pie charts.

  • -
  • This section also covered animation of Matplotlib plots.

  • -
-
-
-

What’s Next

-

The next section introduces more plotting functionality, such as annotations, equation rendering, colormaps, and advanced layout.

-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/matplotlib/matplotlib-basics.html b/_preview/419/core/matplotlib/matplotlib-basics.html deleted file mode 100644 index 6390b1898..000000000 --- a/_preview/419/core/matplotlib/matplotlib-basics.html +++ /dev/null @@ -1,1690 +0,0 @@ - - - - - - - - Matplotlib Basics — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -

Matplotlib logo

-
-

Matplotlib Basics

-
-
-

Overview

-

We will cover the basics of using the Matplotlib library to create plots in Python, including a few different plots available within the library. This page is laid out as follows:

-
    -
  1. Why Matplotlib?

  2. -
  3. Figure and axes

  4. -
  5. Basic line plots

  6. -
  7. Labels and grid lines

  8. -
  9. Customizing colors

  10. -
  11. Subplots

  12. -
  13. Scatterplots

  14. -
  15. Displaying Images

  16. -
  17. Contour and filled contour plots.

  18. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

NumPy Basics

Necessary

MATLAB plotting experience

Helpful

-
    -
  • Time to Learn: 30 minutes

  • -
-
-
-
-

Imports

-

Let’s import the Matplotlib library’s pyplot interface; this interface is the simplest way to create new Matplotlib figures. To shorten this long name, we import it as plt; this helps keep things short, but clear.

-
-
-
import matplotlib.pyplot as plt
-import numpy as np
-
-
-
-
-
-

Info

-

Matplotlib is a Python 2-D plotting library. It is used to produce publication quality figures in a variety of hard-copy formats and interactive environments across platforms.

-
-
-
-

Generate test data using NumPy

-

Here, we generate some test data to use for experimenting with plotting:

-
-
-
times = np.array(
-    [
-        93.0,
-        96.0,
-        99.0,
-        102.0,
-        105.0,
-        108.0,
-        111.0,
-        114.0,
-        117.0,
-        120.0,
-        123.0,
-        126.0,
-        129.0,
-        132.0,
-        135.0,
-        138.0,
-        141.0,
-        144.0,
-        147.0,
-        150.0,
-        153.0,
-        156.0,
-        159.0,
-        162.0,
-    ]
-)
-temps = np.array(
-    [
-        310.7,
-        308.0,
-        296.4,
-        289.5,
-        288.5,
-        287.1,
-        301.1,
-        308.3,
-        311.5,
-        305.1,
-        295.6,
-        292.4,
-        290.4,
-        289.1,
-        299.4,
-        307.9,
-        316.6,
-        293.9,
-        291.2,
-        289.8,
-        287.1,
-        285.8,
-        303.3,
-        310.0,
-    ]
-)
-
-
-
-
-
-
-

Figure and Axes

-

Now, let’s make our first plot with Matplotlib. Matplotlib has two core objects: the Figure and the Axes. The Axes object is an individual plot, containing an x-axis, a y-axis, labels, etc.; it also contains all of the various methods we might use for plotting. A Figure contains one or more Axes objects; it also contains methods for saving plots to files (e.g., PNG, SVG), among other similar high-level functionality. You may find the following diagram helpful:

-

anatomy of a figure

-
-
-

Basic Line Plots

-

Let’s create a Figure whose dimensions, if printed out on hardcopy, would be 10 inches wide and 6 inches long (assuming a landscape orientation). We then create an Axes object, consisting of a single subplot, on the Figure. After that, we call the Axes object’s plot method, using the times array for the data along the x-axis (i.e., the independent values), and the temps array for the data along the y-axis (i.e., the dependent values).

-
-

Info

-

By default, ax.plot will create a line plot, as seen in the following example:

-
-
-
-
# Create a figure
-fig = plt.figure(figsize=(10, 6))
-
-# Ask, out of a 1x1 grid of plots, the first axes.
-ax = fig.add_subplot(1, 1, 1)
-
-# Plot times as x-variable and temperatures as y-variable
-ax.plot(times, temps);
-
-
-
-
-../../_images/513e649f9cb09ff7dd5df7ddab32340274bc98db99647c298f87225fcd29854c.png -
-
-
-
-

Labels and Grid Lines

-
-

Adding labels to an Axes object

-

Next, we add x-axis and y-axis labels to our Axes object, like this:

-
-
-
# Add some labels to the plot
-ax.set_xlabel('Time')
-ax.set_ylabel('Temperature')
-
-# Prompt the notebook to re-display the figure after we modify it
-fig
-
-
-
-
-../../_images/d24c8319bda1f0a93467f96b4fe912c9f7d8e0e83723198d736e4162da5d128b.png -
-
-

We can also add a title to the plot and increase the font size:

-
-
-
ax.set_title('GFS Temperature Forecast', size=16)
-
-fig
-
-
-
-
-../../_images/08ac028742a31a38fadffc5e2aea28dbe25d2e676258735095c5dbc783a5dad0.png -
-
-

There are many other functions and methods associated with Axes objects and labels, but they are too numerous to list here.

-

Here, we set up another test array of temperature data, to be used later:

-
-
-
temps_1000 = np.array(
-    [
-        316.0,
-        316.3,
-        308.9,
-        304.0,
-        302.0,
-        300.8,
-        306.2,
-        309.8,
-        313.5,
-        313.3,
-        308.3,
-        304.9,
-        301.0,
-        299.2,
-        302.6,
-        309.0,
-        311.8,
-        304.7,
-        304.6,
-        301.8,
-        300.6,
-        299.9,
-        306.3,
-        311.3,
-    ]
-)
-
-
-
-
-
-
-

Adding labels and a grid

-

Here, we call plot more than once, in order to plot multiple series of temperature data on the same plot. We also specify the label keyword argument to the plot method to allow Matplotlib to automatically create legend labels. These legend labels are added via a call to the legend method. By utilizing the grid() method, we can also add gridlines to our plot.

-
-
-
fig = plt.figure(figsize=(10, 6))
-ax = fig.add_subplot(1, 1, 1)
-
-# Plot two series of data
-# The label argument is used when generating a legend.
-ax.plot(times, temps, label='Temperature (surface)')
-ax.plot(times, temps_1000, label='Temperature (1000 mb)')
-
-# Add labels and title
-ax.set_xlabel('Time')
-ax.set_ylabel('Temperature')
-ax.set_title('Temperature Forecast')
-
-# Add gridlines
-ax.grid(True)
-
-# Add a legend to the upper left corner of the plot
-ax.legend(loc='upper left');
-
-
-
-
-../../_images/979af31bd706c31017650ee5fb727a56783b09c44f1a657c82b01830871d2fa7.png -
-
-
-
-
-

Customizing colors

-

We’re not restricted to the default look for plot elements. Most plot elements have style attributes, such as linestyle and color, that can be modified to customize the look of a plot. For example, the color attribute can accept a wide array of color options, including keywords (named colors) like red or blue, or HTML color codes. Here, we use some different shades of red taken from the Tableau colorset in Matplotlib, by using the tab:red option for the color attribute.

-
-
-
fig = plt.figure(figsize=(10, 6))
-ax = fig.add_subplot(1, 1, 1)
-
-# Specify how our lines should look
-ax.plot(times, temps, color='tab:red', label='Temperature (surface)')
-ax.plot(
-    times,
-    temps_1000,
-    color='tab:red',
-    linestyle='--',
-    label='Temperature (isobaric level)',
-)
-
-# Set the labels and title
-ax.set_xlabel('Time')
-ax.set_ylabel('Temperature')
-ax.set_title('Temperature Forecast')
-
-# Add the grid
-ax.grid(True)
-
-# Add a legend to the upper left corner of the plot
-ax.legend(loc='upper left');
-
-
-
-
-../../_images/e835ed482076e53833ea5126dbb71e2f668a34e10a74fd8199204f24f9c90cb1.png -
-
-
-
-

Subplots

-

The term “subplots” refers to working with multiple plots, or panels, in a figure.

-

Here, we create yet another set of test data, in this case dew-point data, to be used in later examples:

-
-
-
dewpoint = 0.9 * temps
-dewpoint_1000 = 0.9 * temps_1000
-
-
-
-
-

Now, we can use subplots to plot this new data alongside the temperature data.

-
-

Using add_subplot to create two different subplots within the figure

-

We can use the .add_subplot() method to add subplots to our figure! This method takes the arguments (rows, columns, subplot_number).

-

For example, if we want a single row and two columns, we can use the following code block:

-
-
-
fig = plt.figure(figsize=(10, 6))
-
-# Create a plot for temperature
-ax = fig.add_subplot(1, 2, 1)
-ax.plot(times, temps, color='tab:red')
-
-# Create a plot for dewpoint
-ax2 = fig.add_subplot(1, 2, 2)
-ax2.plot(times, dewpoint, color='tab:green');
-
-
-
-
-../../_images/f7b90da02ddccafe6b7c7ed65b86dea5a12c32741b9e779c00c980c289e6c7af.png -
-
-

You can also call plot.subplots() with the keyword arguments nrows (number of rows) and ncols (number of columns). This initializes a new Axes object, called ax, with the specified number of rows and columns. This object also contains a 1-D list of subplots, with a size equal to nrows x ncols.

-

You can index this list, using ax[0].plot(), for example, to decide which subplot you’re plotting to. Here is some example code for this technique:

-
-
-
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 6))
-
-ax[0].plot(times, temps, color='tab:red')
-ax[1].plot(times, dewpoint, color='tab:green');
-
-
-
-
-../../_images/f7b90da02ddccafe6b7c7ed65b86dea5a12c32741b9e779c00c980c289e6c7af.png -
-
-
-
-

Adding titles to each subplot

-

We can add titles to these plots too; notice that these subplots are titled separately, by calling ax.set_title after plotting each subplot:

-
-
-
fig = plt.figure(figsize=(10, 6))
-
-# Create a plot for temperature
-ax = fig.add_subplot(1, 2, 1)
-ax.plot(times, temps, color='tab:red')
-ax.set_title('Temperature')
-
-# Create a plot for dewpoint
-ax2 = fig.add_subplot(1, 2, 2)
-ax2.plot(times, dewpoint, color='tab:green')
-ax2.set_title('Dewpoint');
-
-
-
-
-../../_images/b375591b1592c2b318f5ebd705ef72a249ba47bdf76bdccb247330278b8aa1bf.png -
-
-
-
-

Using ax.set_xlim and ax.set_ylim to control the plot boundaries

-

It is common when plotting data to set the extent (boundaries) of plots, which can be performed by calling .set_xlim and .set_ylim on the Axes object containing the plot or subplot(s):

-
-
-
fig = plt.figure(figsize=(10, 6))
-
-# Create a plot for temperature
-ax = fig.add_subplot(1, 2, 1)
-ax.plot(times, temps, color='tab:red')
-ax.set_title('Temperature')
-ax.set_xlim(110, 130)
-ax.set_ylim(290, 315)
-
-# Create a plot for dewpoint
-ax2 = fig.add_subplot(1, 2, 2)
-ax2.plot(times, dewpoint, color='tab:green')
-ax2.set_title('Dewpoint')
-ax2.set_xlim(110, 130);
-
-
-
-
-../../_images/e3957a7246fb0d83acceefcb861ebea739d23ab070402577d93dcbddc04869e1.png -
-
-
-
-

Using sharex and sharey to share plot limits

-

You may want to have both subplots share the same x/y axis limits. When setting up a new Axes object through a method like add_subplot, specify the keyword arguments sharex=ax and sharey=ax, where ax is the Axes object with which to share axis limits.

-

Let’s take a look at an example:

-
-
-
fig = plt.figure(figsize=(10, 6))
-
-# Create a plot for temperature
-ax = fig.add_subplot(1, 2, 1)
-ax.plot(times, temps, color='tab:red')
-ax.set_title('Temperature')
-ax.set_ylim(260, 320)
-
-# Create a plot for dewpoint
-ax2 = fig.add_subplot(1, 2, 2, sharex=ax, sharey=ax)
-ax2.plot(times, dewpoint, color='tab:green')
-ax2.set_title('Dewpoint');
-
-
-
-
-../../_images/de77b23271c413fd74a3cd9a7941b7e14bf880ff2e9b7b4c80baf1471dc5796f.png -
-
-
-
-

Putting this all together

-
-

Info

-

If desired, you can move the location of your legend; to do this, specify the loc keyword argument when calling ax.legend().

-
-
-
-
fig = plt.figure(figsize=(10, 6))
-ax = fig.add_subplot(1, 2, 1)
-
-# Specify how our lines should look
-ax.plot(times, temps, color='tab:red', label='Temperature (surface)')
-ax.plot(
-    times,
-    temps_1000,
-    color='tab:red',
-    linestyle=':',
-    label='Temperature (isobaric level)',
-)
-
-# Add labels, grid, and legend
-ax.set_xlabel('Time')
-ax.set_ylabel('Temperature')
-ax.set_title('Temperature Forecast')
-ax.grid(True)
-ax.legend(loc='upper left')
-ax.set_ylim(257, 312)
-ax.set_xlim(95, 162)
-
-
-# Add our second plot - for dewpoint, changing the colors and labels
-ax2 = fig.add_subplot(1, 2, 2, sharex=ax, sharey=ax)
-ax2.plot(times, dewpoint, color='tab:green', label='Dewpoint (surface)')
-ax2.plot(
-    times,
-    dewpoint_1000,
-    color='tab:green',
-    linestyle=':',
-    marker='o',
-    label='Dewpoint (isobaric level)',
-)
-
-ax2.set_xlabel('Time')
-ax2.set_ylabel('Dewpoint')
-ax2.set_title('Dewpoint Forecast')
-ax2.grid(True)
-ax2.legend(loc='upper left');
-
-
-
-
-../../_images/c593846a966886929bd8a0ccb6762de4a5e5595aca6e3a97d02a9206935feae9.png -
-
-
-
-
-

Scatterplot

-

Some data cannot be plotted accurately as a line plot. Another type of plot that is popular in science is the marker plot, more commonly known as a scatter plot. A simple scatter plot can be created by setting the linestyle to None, and specifying a marker type, size, color, etc., like this:

-
-
-
fig = plt.figure(figsize=(10, 6))
-ax = fig.add_subplot(1, 1, 1)
-
-# Specify no line with circle markers
-ax.plot(temps, temps_1000, linestyle='None', marker='o', markersize=5)
-
-ax.set_xlabel('Temperature (surface)')
-ax.set_ylabel('Temperature (1000 hPa)')
-ax.set_title('Temperature Cross Plot')
-ax.grid(True);
-
-
-
-
-../../_images/bdaf6a3686c82bc6b2f2854849a73d0134030236328f61b255c026816f52970c.png -
-
-
-

Info

-

You can also use the scatter method, which is slower, but will give you more control, such as being able to color the points individually based upon a third variable.

-
-
-
-
fig = plt.figure(figsize=(10, 6))
-ax = fig.add_subplot(1, 1, 1)
-
-# Specify no line with circle markers
-ax.scatter(temps, temps_1000)
-
-ax.set_xlabel('Temperature (surface)')
-ax.set_ylabel('Temperature (1000 hPa)')
-ax.set_title('Temperature Cross Plot')
-ax.grid(True);
-
-
-
-
-../../_images/fe028b4765ff40d9fe068f2662802ad65b6aa0fc1d72cc21dc786cedd2d6dc8e.png -
-
-

Let’s put together the following:

-
    -
  • Beginning with our code above, add the c keyword argument to the scatter call; in this case, to color the points by the difference between the temperature at the surface and the temperature at 1000 hPa.

  • -
  • Add a 1:1 line to the plot (slope of 1, intercept of zero). Use a black dashed line.

  • -
  • Change the colormap to one more suited for a temperature-difference plot.

  • -
  • Add a colorbar to the plot (have a look at the Matplotlib documentation for help).

  • -
-
-
-
fig = plt.figure(figsize=(10, 6))
-ax = fig.add_subplot(1, 1, 1)
-
-ax.plot([285, 320], [285, 320], color='black', linestyle='--')
-s = ax.scatter(temps, temps_1000, c=(temps - temps_1000), cmap='bwr', vmin=-5, vmax=5)
-fig.colorbar(s)
-
-ax.set_xlabel('Temperature (surface)')
-ax.set_ylabel('Temperature (1000 hPa)')
-ax.set_title('Temperature Cross Plot')
-ax.grid(True);
-
-
-
-
-../../_images/2c9cc26aa429f9c024a5782a4345881da049b471feca4c6eae4ddf92bee3b3f1.png -
-
-
-
-

Displaying Images

-

imshow displays the values in an array as colored pixels, similar to a heat map.

-

Here, we declare some fake data in a bivariate normal distribution, to illustrate the imshow method:

-
-
-
x = y = np.arange(-3.0, 3.0, 0.025)
-X, Y = np.meshgrid(x, y)
-Z1 = np.exp(-(X**2) - Y**2)
-Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
-Z = (Z1 - Z2) * 2
-
-
-
-
-

We can now pass this fake data to imshow to create a heat map of the distribution:

-
-
-
fig, ax = plt.subplots()
-im = ax.imshow(
-    Z, interpolation='bilinear', cmap='RdYlGn', origin='lower', extent=[-3, 3, -3, 3]
-)
-
-
-
-
-../../_images/3cd6d69dc83a2f6aa2262ba51fe6206c61067f9991da6a671ad8cab02787a894.png -
-
-
-
-

Contour and Filled Contour Plots

-
    -
  • contour creates contours around data.

  • -
  • contourf creates filled contours around data.

  • -
-

Let’s start with the contour method, which, as just mentioned, creates contours around data:

-
-
-
fig, ax = plt.subplots()
-ax.contour(X, Y, Z);
-
-
-
-
-../../_images/01e31da42bf1fe6a73bc4223fe1645048d12e4d175d92522b1bf662052ea7cd3.png -
-
-

After creating contours, we can label the lines using the clabel method, like this:

-
-
-
fig, ax = plt.subplots()
-c = ax.contour(X, Y, Z, levels=np.arange(-2, 2, 0.25))
-ax.clabel(c);
-
-
-
-
-../../_images/3210548ea930f45d2c7e5191fe64f6b63bac227782c56ad6e968a3d44274d0a5.png -
-
-

As described above, the contourf (contour fill) method creates filled contours around data, like this:

-
-
-
fig, ax = plt.subplots()
-c = ax.contourf(X, Y, Z);
-
-
-
-
-../../_images/99367f9cf2fb967975cd32eb969656dabdc05991adfe50f8db2f9f1c02f3ae96.png -
-
-

As a final example, let’s create a heatmap figure with contours using the contour and imshow methods. First, we use imshow to create the heatmap, specifying a colormap using the cmap keyword argument. We then call contour, specifying black contours and an interval of 0.5. Here is the example code, and resulting figure:

-
-
-
fig, ax = plt.subplots()
-im = ax.imshow(
-    Z, interpolation='bilinear', cmap='PiYG', origin='lower', extent=[-3, 3, -3, 3]
-)
-c = ax.contour(X, Y, Z, levels=np.arange(-2, 2, 0.5), colors='black')
-ax.clabel(c);
-
-
-
-
-../../_images/e603b7f2c8eb3176fe23317457122b7747bc039ce5c3196a519d2ae39489d14b.png -
-
-
-
-
-

Summary

-
    -
  • Matplotlib can be used to visualize datasets you are working with.

  • -
  • You can customize various features such as labels and styles.

  • -
  • There are a wide variety of plotting options available, including (but not limited to):

    -
      -
    • Line plots (plot)

    • -
    • Scatter plots (scatter)

    • -
    • Heatmaps (imshow)

    • -
    • Contour line and contour fill plots (contour, contourf)

    • -
    -
  • -
-
-
-

What’s Next?

-

In the next section, more plotting functionality is covered, such as histograms, pie charts, and animation.

-
-
-

Resources and References

-

The goal of this tutorial is to provide an overview of the use of the Matplotlib library. It covers creating simple line plots, but it is by no means comprehensive. For more information, try looking at the following documentation:

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/numpy.html b/_preview/419/core/numpy.html deleted file mode 100644 index 85eff4b9c..000000000 --- a/_preview/419/core/numpy.html +++ /dev/null @@ -1,857 +0,0 @@ - - - - - - - - NumPy — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -

NumPy Logo

-
-

NumPy

-

This section contains tutorials on array computing with NumPy.

-
-

From the NumPy documentation:

-
-

NumPy is the fundamental package for scientific computing in Python. It is a Python library that provides a multidimensional array object, various derived objects (such as masked arrays and matrices), and an assortment of routines for fast operations on arrays, including mathematical, logical, shape manipulation, sorting, selecting, I/O, discrete Fourier transforms, basic linear algebra, basic statistical operations, random simulation, and much more.

-
-

NumPy’s position at the center of the scientific Python ecosystem means that all users should start here in their learning journey through the core scientific packages.

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/numpy/intermediate-numpy.html b/_preview/419/core/numpy/intermediate-numpy.html deleted file mode 100644 index 757b84f0a..000000000 --- a/_preview/419/core/numpy/intermediate-numpy.html +++ /dev/null @@ -1,1544 +0,0 @@ - - - - - - - - Intermediate NumPy — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -

NumPy Logo

-
-

Intermediate NumPy

-
-
-

Overview

-
    -
  1. Working with multiple dimensions

  2. -
  3. Subsetting of irregular arrays with booleans

  4. -
  5. Sorting, or indexing with indices

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

NumPy Basics

Necessary

-
    -
  • Time to learn: 20 minutes

  • -
-
-
-
-

Imports

-

We will be including Matplotlib to illustrate some of our examples, but you don’t need knowledge of it to complete this notebook.

-
-
-
import matplotlib.pyplot as plt
-import numpy as np
-
-
-
-
-
-
-

Using axes to slice arrays

-

Here we introduce an important concept when working with NumPy: the axis. This indicates the particular dimension along which a function should operate (provided the function does something taking multiple values and converts to a single value).

-

Let’s look at a concrete example with sum:

-
-
-
a = np.arange(12).reshape(3, 4)
-a
-
-
-
-
-
array([[ 0,  1,  2,  3],
-       [ 4,  5,  6,  7],
-       [ 8,  9, 10, 11]])
-
-
-
-
-

This calculates the total of all values in the array.

-
-
-
np.sum(a)
-
-
-
-
-
66
-
-
-
-
-
-

Info

-

Some of NumPy’s functions can be accessed as ndarray methods!

-
-
-
-
a.sum()
-
-
-
-
-
66
-
-
-
-
-

Now, with a reminder about how our array is shaped,

-
-
-
a.shape
-
-
-
-
-
(3, 4)
-
-
-
-
-

we can specify axis to get just the sum across each of our rows.

-
-
-
np.sum(a, axis=0)
-
-
-
-
-
array([12, 15, 18, 21])
-
-
-
-
-

Or do the same and take the sum across columns:

-
-
-
np.sum(a, axis=1)
-
-
-
-
-
array([ 6, 22, 38])
-
-
-
-
-

After putting together some data and introducing some more advanced calculations, let’s demonstrate a multi-layered example: calculating temperature advection. If you’re not familiar with this (don’t worry!), we’ll be looking to calculate

-
-\[\begin{equation*} -\text{advection} = -\vec{v} \cdot \nabla T -\end{equation*}\]
-

and to do so we’ll start with some random \(T\) and \(\vec{v}\) values,

-
-
-
temp = np.random.randn(100, 50)
-u = np.random.randn(100, 50)
-v = np.random.randn(100, 50)
-
-
-
-
-

We can calculate the np.gradient of our new \(T(100x50)\) field as two separate component gradients,

-
-
-
gradient_x, gradient_y = np.gradient(temp)
-
-
-
-
-

In order to calculate \(-\vec{v} \cdot \nabla T\), we will use np.dstack to turn our two separate component gradient fields into one multidimensional field containing \(x\) and \(y\) gradients at each of our \(100x50\) points,

-
-
-
grad_vectors = np.dstack([gradient_x, gradient_y])
-print(grad_vectors.shape)
-
-
-
-
-
(100, 50, 2)
-
-
-
-
-

and then do the same for our separate \(u\) and \(v\) wind components,

-
-
-
wind_vectors = np.dstack([u, v])
-print(wind_vectors.shape)
-
-
-
-
-
(100, 50, 2)
-
-
-
-
-

Finally, we can calculate the dot product of these two multidimensional fields of wind and temperature gradient components by hand as an element-wise multiplication, *, and then a sum of our separate components at each point (i.e., along the last axis),

-
-
-
advection = (wind_vectors * -grad_vectors).sum(axis=-1)
-print(advection.shape)
-
-
-
-
-
(100, 50)
-
-
-
-
-
-
-

Indexing arrays with boolean values

-
-

Array comparisons

-

NumPy can easily create arrays of boolean values and use those to select certain values to extract from an array

-
-
-
# Create some synthetic data representing temperature and wind speed data
-np.random.seed(19990503)  # Make sure we all have the same data
-temp = 20 * np.cos(np.linspace(0, 2 * np.pi, 100)) + 50 + 2 * np.random.randn(100)
-speed = np.abs(
-    10 * np.sin(np.linspace(0, 2 * np.pi, 100)) + 10 + 5 * np.random.randn(100)
-)
-
-
-
-
-
-
-
plt.plot(temp, 'tab:red')
-plt.plot(speed, 'tab:blue');
-
-
-
-
-../../_images/96d227e0ce7f4673aa8f4f6a669afd6ad9ef7b90a5428f30126194e2caae2f1b.png -
-
-

By doing a comparison between a NumPy array and a value, we get an -array of values representing the results of the comparison between -each element and the value

-
-
-
temp > 45
-
-
-
-
-
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True, False, False,  True, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False,  True, False, False, False, False,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True])
-
-
-
-
-

This, which is its own NumPy array of boolean values, can be used as an index to another array of the same size. We can even use it as an index within the original temp array we used to compare,

-
-
-
temp[temp > 45]
-
-
-
-
-
array([69.89825854, 71.52313905, 69.90028363, 66.73828667, 66.77980233,
-       72.91468564, 69.34603239, 69.09533591, 68.27350814, 64.33916721,
-       67.49497791, 67.05282372, 63.51829518, 63.54034678, 65.46576463,
-       62.99683836, 59.27662304, 61.29361272, 60.51641586, 57.46048995,
-       55.19793004, 53.07572989, 54.47998158, 53.09552107, 54.59037269,
-       47.84272747, 49.1435589 , 45.87151534, 45.11976794, 45.009292  ,
-       46.36021141, 46.87557425, 47.25668992, 50.09599544, 53.77789358,
-       50.24073197, 54.07629059, 51.95065202, 55.84827794, 57.56967086,
-       57.19572063, 61.67658285, 56.51474577, 59.72166924, 62.99403256,
-       63.57569453, 64.05984232, 60.88258643, 65.37759899, 63.94115754,
-       65.53070256, 67.15175649, 66.26468701, 67.03811793, 69.17773618,
-       69.83571708, 70.99586742, 66.34971928, 67.49905207, 69.83593609])
-
-
-
-
-
-

Info

-

This only returns the values from our original array meeting the indexing conditions, nothing more! Note the size,

-
-
-
-
temp[temp > 45].shape
-
-
-
-
-
(60,)
-
-
-
-
-
-

Warning

-

Indexing arrays with arrays requires them to be the same size!

-
-

If we store this array somewhere new,

-
-
-
temp_45 = temp[temp > 45]
-
-
-
-
-
-
-
temp_45[temp < 45]
-
-
-
-
-
---------------------------------------------------------------------------
-IndexError                                Traceback (most recent call last)
-Cell In[19], line 1
-----> 1 temp_45[temp < 45]
-
-IndexError: boolean index did not match indexed array along dimension 0; dimension is 60 but corresponding boolean dimension is 100
-
-
-
-
-

We find that our original (100,) shape array is too large to subset our new (60,) array.

-

If their sizes do match, the boolean array can come from a totally different array!

-
-
-
speed > 10
-
-
-
-
-
array([False, False, False,  True,  True, False,  True,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True,  True,  True,  True,  True,  True,  True,  True,  True,
-        True,  True,  True, False,  True,  True,  True,  True, False,
-        True,  True,  True,  True, False,  True,  True,  True,  True,
-        True,  True, False,  True,  True, False,  True, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False,  True, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False,  True,  True, False, False, False,
-        True])
-
-
-
-
-
-
-
temp[speed > 10]
-
-
-
-
-
array([66.73828667, 66.77980233, 69.34603239, 69.09533591, 68.27350814,
-       64.33916721, 67.49497791, 67.05282372, 63.51829518, 63.54034678,
-       65.46576463, 62.99683836, 59.27662304, 61.29361272, 60.51641586,
-       57.46048995, 55.19793004, 53.07572989, 54.47998158, 53.09552107,
-       54.59037269, 47.84272747, 49.1435589 , 45.87151534, 43.95971516,
-       42.72814762, 42.45316175, 39.2797517 , 40.23351938, 36.77179678,
-       34.43329229, 31.42277612, 38.97505745, 34.10549575, 35.70826448,
-       29.01276068, 30.31180935, 29.31602671, 32.84580454, 30.76695309,
-       29.11344716, 30.16652571, 29.91513049, 39.51784389, 69.17773618,
-       69.83571708, 69.83593609])
-
-
-
-
-
-
-

Replacing values

-

To extend this, we can use this conditional indexing to assign new values to certain positions within our array, somewhat like a masking operation.

-
-
-
# Make a copy so we don't modify the original data
-temp2 = temp.copy()
-speed2 = speed.copy()
-
-# Replace all places where speed is <10 with NaN (not a number)
-temp2[speed < 10] = np.nan
-speed2[speed < 10] = np.nan
-
-
-
-
-
-
-
plt.plot(temp2, 'tab:red');
-
-
-
-
-../../_images/dbbd81aa3ad7ceca6264932bbf192282e1e18aa8ee7e84dd27f0bf4c4b3eef6a.png -
-
-

and to put this in context,

-
-
-
plt.plot(temp, 'r:')
-plt.plot(temp2, 'r')
-plt.plot(speed, 'b:')
-plt.plot(speed2, 'b');
-
-
-
-
-../../_images/92cee845d2b7db2398332636d7519f910cb20342b10768e48796ccf6604dfff0.png -
-
-

If we use parentheses to preserve the order of operations, we can combine these conditions with other bitwise operators like the & for bitwise_and,

-
-
-
multi_mask = (temp < 45) & (speed > 10)
-multi_mask
-
-
-
-
-
array([False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False,  True,  True, False,  True,  True,  True,  True, False,
-        True,  True,  True,  True, False,  True,  True,  True,  True,
-        True,  True, False,  True,  True, False,  True, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False,  True, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False, False, False, False, False, False, False, False, False,
-       False])
-
-
-
-
-
-
-
temp[multi_mask]
-
-
-
-
-
array([43.95971516, 42.72814762, 42.45316175, 39.2797517 , 40.23351938,
-       36.77179678, 34.43329229, 31.42277612, 38.97505745, 34.10549575,
-       35.70826448, 29.01276068, 30.31180935, 29.31602671, 32.84580454,
-       30.76695309, 29.11344716, 30.16652571, 29.91513049, 39.51784389])
-
-
-
-
-

Heat index is only defined for temperatures >= 80F and relative humidity values >= 40%. Using the data generated below, we can use boolean indexing to extract the data where heat index has a valid value.

-
-
-
# Here's the "data"
-np.random.seed(19990503)
-temp = 20 * np.cos(np.linspace(0, 2 * np.pi, 100)) + 80 + 2 * np.random.randn(100)
-relative_humidity = np.abs(
-    20 * np.cos(np.linspace(0, 4 * np.pi, 100)) + 50 + 5 * np.random.randn(100)
-)
-
-# Create a mask for the two conditions described above
-good_heat_index = (temp >= 80) & (relative_humidity >= 0.4)
-
-# Use this mask to grab the temperature and relative humidity values that together
-# will give good heat index values
-print(temp[good_heat_index])
-
-
-
-
-
[ 99.89825854 101.52313905  99.90028363  96.73828667  96.77980233
- 102.91468564  99.34603239  99.09533591  98.27350814  94.33916721
-  97.49497791  97.05282372  93.51829518  93.54034678  95.46576463
-  92.99683836  89.27662304  91.29361272  90.51641586  87.46048995
-  85.19793004  83.07572989  84.47998158  83.09552107  84.59037269
-  80.09599544  83.77789358  80.24073197  84.07629059  81.95065202
-  85.84827794  87.56967086  87.19572063  91.67658285  86.51474577
-  89.72166924  92.99403256  93.57569453  94.05984232  90.88258643
-  95.37759899  93.94115754  95.53070256  97.15175649  96.26468701
-  97.03811793  99.17773618  99.83571708 100.99586742  96.34971928
-  97.49905207  99.83593609]
-
-
-
-
-

Another bitwise operator we can find helpful is Python’s ~ complement operator, which can give us the inverse of our specific mask to let us assign np.nan to every value not satisfied in good_heat_index.

-
-
-
plot_temp = temp.copy()
-plot_temp[~good_heat_index] = np.nan
-plt.plot(plot_temp, 'tab:red');
-
-
-
-
-../../_images/537f068f58fff14b531b3d040b26422c59c31388d3a46faccaee3b96749a2221.png -
-
-
-
-
-

Indexing using arrays of indices

-

You can also use a list or array of indices to extract particular values–this is a natural extension of the regular indexing. For instance, just as we can select the first element:

-
-
-
temp[0]
-
-
-
-
-
99.89825854468695
-
-
-
-
-

We can also extract the first, fifth, and tenth elements as a list:

-
-
-
temp[[0, 4, 9]]
-
-
-
-
-
array([99.89825854, 96.77980233, 94.33916721])
-
-
-
-
-

One of the ways this comes into play is trying to sort NumPy arrays using argsort. This function returns the indices of the array that give the items in sorted order. So for our temp,

-
-
-
inds = np.argsort(temp)
-inds
-
-
-
-
-
array([52, 57, 42, 48, 54, 44, 56, 51, 49, 43, 50, 46, 58, 55, 53, 40, 37,
-       61, 47, 45, 59, 39, 36, 60, 41, 34, 66, 63, 35, 38, 32, 62, 64, 33,
-       31, 67, 29, 28, 68, 69, 65, 30, 27, 70, 71, 72, 25, 26, 73, 75, 77,
-       21, 23, 74, 76, 22, 24, 20, 78, 82, 80, 19, 79, 16, 83, 18, 87, 17,
-       81, 84, 15, 12, 13, 85, 89, 86,  9, 88, 14, 90, 92, 97,  3,  4, 93,
-       11, 91, 10, 98,  8,  7, 94,  6, 95, 99,  0,  2, 96,  1,  5])
-
-
-
-
-

i.e., our lowest value is at index 52, next 57, and so on. We can use this array of indices as an index for temp,

-
-
-
temp[inds]
-
-
-
-
-
array([ 58.71828204,  58.85269149,  59.01276068,  59.11344716,
-        59.25186164,  59.31602671,  59.42796381,  59.91513049,
-        60.16652571,  60.31180935,  60.48608715,  60.76695309,
-        60.93380275,  60.95814392,  61.07199963,  61.1341411 ,
-        61.42277612,  62.27369636,  62.44927684,  62.84580454,
-        63.37573713,  64.10549575,  64.43329229,  64.95696914,
-        65.70826448,  66.77179678,  67.06954335,  67.39853293,
-        67.7453367 ,  68.97505745,  69.2797517 ,  69.34620461,
-        69.51784389,  70.23351938,  72.45316175,  72.69583703,
-        72.72814762,  73.95971516,  74.03576453,  74.45775806,
-        75.009292  ,  75.11976794,  75.87151534,  76.36021141,
-        76.87557425,  77.25668992,  77.84272747,  79.1435589 ,
-        80.09599544,  80.24073197,  81.95065202,  83.07572989,
-        83.09552107,  83.77789358,  84.07629059,  84.47998158,
-        84.59037269,  85.19793004,  85.84827794,  86.51474577,
-        87.19572063,  87.46048995,  87.56967086,  89.27662304,
-        89.72166924,  90.51641586,  90.88258643,  91.29361272,
-        91.67658285,  92.99403256,  92.99683836,  93.51829518,
-        93.54034678,  93.57569453,  93.94115754,  94.05984232,
-        94.33916721,  95.37759899,  95.46576463,  95.53070256,
-        96.26468701,  96.34971928,  96.73828667,  96.77980233,
-        97.03811793,  97.05282372,  97.15175649,  97.49497791,
-        97.49905207,  98.27350814,  99.09533591,  99.17773618,
-        99.34603239,  99.83571708,  99.83593609,  99.89825854,
-        99.90028363, 100.99586742, 101.52313905, 102.91468564])
-
-
-
-
-

to get a sorted array back!

-

With some clever slicing, we can pull out the last 10, or 10 highest, values of temp,

-
-
-
ten_highest = inds[-10:]
-print(temp[ten_highest])
-
-
-
-
-
[ 99.09533591  99.17773618  99.34603239  99.83571708  99.83593609
-  99.89825854  99.90028363 100.99586742 101.52313905 102.91468564]
-
-
-
-
-

There are other NumPy arg functions that return indices for operating; check out the NumPy docs on sorting your arrays!

-
-
-
-

Summary

-

In this notebook we introduced the power of understanding the dimensions of our data by specifying math along axis, used True and False values to subset our data according to conditions, and used lists of positions within our array to sort our data.

-
-

What’s Next

-

Taking some time to practice this is valuable to be able to quickly manipulate arrays of information in useful or scientific ways.

-
-
-
-

Resources and references

-

The NumPy Users Guide expands further on some of these topics, as well as suggests various Tutorials, lectures, and more at this stage.

-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/numpy/numpy-basics.html b/_preview/419/core/numpy/numpy-basics.html deleted file mode 100644 index 58fba0f27..000000000 --- a/_preview/419/core/numpy/numpy-basics.html +++ /dev/null @@ -1,1922 +0,0 @@ - - - - - - - - NumPy Basics — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -

NumPy Logo

-
-

NumPy Basics

-
-
-

Overview

-

NumPy is the fundamental package for scientific computing with Python. It contains among other things:

-
    -
  • a powerful N-dimensional array object

  • -
  • sophisticated (broadcasting) functions

  • -
  • useful linear algebra, Fourier transform, and random number capabilities

  • -
-

The NumPy array object is the common interface for working with typed arrays of data across a wide-variety of scientific Python packages. NumPy also features a C-API, which enables interfacing existing Fortran/C/C++ libraries with Python and NumPy. In this notebook we will cover

-
    -
  1. Creating an array

  2. -
  3. Math and calculations with arrays

  4. -
  5. Inspecting an array with slicing and indexing

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Python Quickstart

Necessary

Lists, indexing, slicing, math

-
    -
  • Time to learn: 35 minutes

  • -
-
-
-
-

Imports

-

A common convention you might encounter is to rename numpy to np on import to shorten it for the many times we will be calling on numpy for functionality.

-
-
-
import numpy as np
-
-
-
-
-
-
-

Create an array of ‘data’

-

The NumPy array represents a contiguous block of memory, holding entries of a given type (and hence fixed size). The entries are laid out in memory according to the shape, or list of dimension sizes. Let’s start by creating an array from a list of integers and taking a look at it,

-
-
-
a = np.array([1, 2, 3])
-a
-
-
-
-
-
array([1, 2, 3])
-
-
-
-
-

We can inspect the number of dimensions our array is organized along with ndim, and how long each of these dimensions are with shape

-
-
-
a.ndim
-
-
-
-
-
1
-
-
-
-
-
-
-
a.shape
-
-
-
-
-
(3,)
-
-
-
-
-

So our 1-dimensional array has a shape of 3 along that dimension! Finally we can check out the underlying type of our underlying data,

-
-
-
a.dtype
-
-
-
-
-
dtype('int64')
-
-
-
-
-

Now, let’s expand this with a new data type, and by using a list of lists we can grow the dimensions of our array!

-
-
-
a = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
-a
-
-
-
-
-
array([[1., 2., 3.],
-       [4., 5., 6.]])
-
-
-
-
-
-
-
a.ndim
-
-
-
-
-
2
-
-
-
-
-
-
-
a.shape
-
-
-
-
-
(2, 3)
-
-
-
-
-
-
-
a.dtype
-
-
-
-
-
dtype('float64')
-
-
-
-
-

And as before we can use ndim, shape, and dtype to discover how many dimensions of what lengths are making up our array of floats.

-
-

Generation

-

NumPy also provides helper functions for generating arrays of data to save you typing for regularly spaced data. Don’t forget your Python indexing rules!

-
    -
  • arange(start, stop, step) creates a range of values in the interval [start,stop) with step spacing.

  • -
  • linspace(start, stop, num) creates a range of num evenly spaced values over the range [start,stop].

  • -
-
-

arange

-
-
-
a = np.arange(5)
-a
-
-
-
-
-
array([0, 1, 2, 3, 4])
-
-
-
-
-
-
-
a = np.arange(3, 11)
-a
-
-
-
-
-
array([ 3,  4,  5,  6,  7,  8,  9, 10])
-
-
-
-
-
-
-
a = np.arange(1, 10, 2)
-a
-
-
-
-
-
array([1, 3, 5, 7, 9])
-
-
-
-
-
-
-

linspace

-
-
-
b = np.linspace(0, 4, 5)
-b
-
-
-
-
-
array([0., 1., 2., 3., 4.])
-
-
-
-
-
-
-
b.shape
-
-
-
-
-
(5,)
-
-
-
-
-
-
-
b = np.linspace(3, 10, 15)
-b
-
-
-
-
-
array([ 3. ,  3.5,  4. ,  4.5,  5. ,  5.5,  6. ,  6.5,  7. ,  7.5,  8. ,
-        8.5,  9. ,  9.5, 10. ])
-
-
-
-
-
-
-
b = np.linspace(2.5, 10.25, 11)
-b
-
-
-
-
-
array([ 2.5  ,  3.275,  4.05 ,  4.825,  5.6  ,  6.375,  7.15 ,  7.925,
-        8.7  ,  9.475, 10.25 ])
-
-
-
-
-
-
-
b = np.linspace(0, 100, 30)
-b
-
-
-
-
-
array([  0.        ,   3.44827586,   6.89655172,  10.34482759,
-        13.79310345,  17.24137931,  20.68965517,  24.13793103,
-        27.5862069 ,  31.03448276,  34.48275862,  37.93103448,
-        41.37931034,  44.82758621,  48.27586207,  51.72413793,
-        55.17241379,  58.62068966,  62.06896552,  65.51724138,
-        68.96551724,  72.4137931 ,  75.86206897,  79.31034483,
-        82.75862069,  86.20689655,  89.65517241,  93.10344828,
-        96.55172414, 100.        ])
-
-
-
-
-
-
-
-
-

Perform calculations with NumPy

-
-

Arithmetic

-

In core Python, that is without NumPy, creating sequences of values and adding them together requires writing a lot of manual loops, just like one would do in C/C++:

-
-
-
a = list(range(5, 10))
-b = [3 + i * 1.5 / 4 for i in range(5)]
-
-a, b
-
-
-
-
-
([5, 6, 7, 8, 9], [3.0, 3.375, 3.75, 4.125, 4.5])
-
-
-
-
-
-
-
result = []
-for x, y in zip(a, b):
-    result.append(x + y)
-print(result)
-
-
-
-
-
[8.0, 9.375, 10.75, 12.125, 13.5]
-
-
-
-
-

That is very verbose and not very intuitive. Using NumPy this becomes:

-
-
-
a = np.arange(5, 10)
-b = np.linspace(3, 4.5, 5)
-
-
-
-
-
-
-
a + b
-
-
-
-
-
array([ 8.   ,  9.375, 10.75 , 12.125, 13.5  ])
-
-
-
-
-

Many major mathematical operations operate in the same way. They perform an element-by-element calculation of the two arrays.

-
-
-
a - b
-
-
-
-
-
array([2.   , 2.625, 3.25 , 3.875, 4.5  ])
-
-
-
-
-
-
-
a / b
-
-
-
-
-
array([1.66666667, 1.77777778, 1.86666667, 1.93939394, 2.        ])
-
-
-
-
-
-
-
a**b
-
-
-
-
-
array([  125.        ,   422.92218768,  1476.10635524,  5311.85481585,
-       19683.        ])
-
-
-
-
-
-

Warning

-

These arrays must be the same shape!

-
-
-
-
b = np.linspace(3, 4.5, 6)
-a.shape, b.shape
-
-
-
-
-
((5,), (6,))
-
-
-
-
-
-
-
a * b
-
-
-
-
-
---------------------------------------------------------------------------
-ValueError                                Traceback (most recent call last)
-Cell In[26], line 1
-----> 1 a * b
-
-ValueError: operands could not be broadcast together with shapes (5,) (6,) 
-
-
-
-
-
-
-

Constants

-

NumPy provides us access to some useful constants as well - remember you should never be typing these in manually! Other libraries such as SciPy and MetPy have their own set of constants that are more domain specific.

-
-
-
np.pi
-
-
-
-
-
3.141592653589793
-
-
-
-
-
-
-
np.e
-
-
-
-
-
2.718281828459045
-
-
-
-
-

You can use these for classic calculations you might be familiar with! Here we can create a range t = [0, 2 pi] by pi/4,

-
-
-
t = np.arange(0, 2 * np.pi + np.pi / 4, np.pi / 4)
-t
-
-
-
-
-
array([0.        , 0.78539816, 1.57079633, 2.35619449, 3.14159265,
-       3.92699082, 4.71238898, 5.49778714, 6.28318531])
-
-
-
-
-
-
-
t / np.pi
-
-
-
-
-
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])
-
-
-
-
-
-
-

Array math functions

-

NumPy also has math functions that can operate on arrays. Similar to the math operations, these greatly simplify and speed up these operations. Let’s start with calculating \(\sin(t)\)!

-
-
-
sin_t = np.sin(t)
-sin_t
-
-
-
-
-
array([ 0.00000000e+00,  7.07106781e-01,  1.00000000e+00,  7.07106781e-01,
-        1.22464680e-16, -7.07106781e-01, -1.00000000e+00, -7.07106781e-01,
-       -2.44929360e-16])
-
-
-
-
-

and clean it up a bit by rounding to three decimal places.

-
-
-
np.round(sin_t, 3)
-
-
-
-
-
array([ 0.   ,  0.707,  1.   ,  0.707,  0.   , -0.707, -1.   , -0.707,
-       -0.   ])
-
-
-
-
-
-
-
cos_t = np.cos(t)
-cos_t
-
-
-
-
-
array([ 1.00000000e+00,  7.07106781e-01,  6.12323400e-17, -7.07106781e-01,
-       -1.00000000e+00, -7.07106781e-01, -1.83697020e-16,  7.07106781e-01,
-        1.00000000e+00])
-
-
-
-
-
-

Info

-

Check out NumPy’s list of mathematical functions here!

-
-

We can convert between degrees and radians with only NumPy, by hand

-
-
-
t / np.pi * 180
-
-
-
-
-
array([  0.,  45.,  90., 135., 180., 225., 270., 315., 360.])
-
-
-
-
-

or with built-in function rad2deg,

-
-
-
degrees = np.rad2deg(t)
-degrees
-
-
-
-
-
array([  0.,  45.,  90., 135., 180., 225., 270., 315., 360.])
-
-
-
-
-

We are similarly provided algorithms for operations including integration, bulk summing, and cumulative summing.

-
-
-
sine_integral = np.trapz(sin_t, t)
-np.round(sine_integral, 3)
-
-
-
-
-
-0.0
-
-
-
-
-
-
-
cos_sum = np.sum(cos_t)
-cos_sum
-
-
-
-
-
0.9999999999999996
-
-
-
-
-
-
-
cos_csum = np.cumsum(cos_t)
-print(cos_csum)
-
-
-
-
-
[ 1.00000000e+00  1.70710678e+00  1.70710678e+00  1.00000000e+00
-  0.00000000e+00 -7.07106781e-01 -7.07106781e-01 -5.55111512e-16
-  1.00000000e+00]
-
-
-
-
-
-
-
-

Indexing and subsetting arrays

-
-

Indexing

-

We can use integer indexing to reach into our arrays and pull out individual elements. Let’s make a toy 2-d array to explore. Here we create a 12-value arange and reshape it into a 3x4 array.

-
-
-
a = np.arange(12).reshape(3, 4)
-a
-
-
-
-
-
array([[ 0,  1,  2,  3],
-       [ 4,  5,  6,  7],
-       [ 8,  9, 10, 11]])
-
-
-
-
-

Recall that Python indexing starts at 0, and we can begin indexing our array with the list-style list[element] notation,

-
-
-
a[0]
-
-
-
-
-
array([0, 1, 2, 3])
-
-
-
-
-

to pull out just our first row of data within a. Similarly we can index in reverse with negative indices,

-
-
-
a[-1]
-
-
-
-
-
array([ 8,  9, 10, 11])
-
-
-
-
-

to pull out just the last row of data within a. This notation extends to as many dimensions as make up our array as array[m, n, p, ...]. The following diagram shows these indices for an example, 2-dimensional 6x6 array,

-

-

For example, let’s find the entry in our array corresponding to the 2nd row (m=1 in Python) and the 3rd column (n=2 in Python)

-
-
-
a[1, 2]
-
-
-
-
-
6
-
-
-
-
-

We can again use these negative indices to index backwards,

-
-
-
a[-1, -1]
-
-
-
-
-
11
-
-
-
-
-

and even mix-and-match along dimensions,

-
-
-
a[1, -2]
-
-
-
-
-
6
-
-
-
-
-
-
-

Slices

-

Slicing syntax is written as array[start:stop[:step]], where all numbers are optional.

-
    -
  • defaults:

    -
      -
    • start = 0

    • -
    • stop = len(dim)

    • -
    • step = 1

    • -
    -
  • -
  • The second colon is also optional if no step is used.

  • -
-

Let’s pull out just the first row, m=0 of a and see how this works!

-
-
-
b = a[0]
-b
-
-
-
-
-
array([0, 1, 2, 3])
-
-
-
-
-

Laying out our default slice to see the entire array explicitly looks something like this,

-
-
-
b[0:4:1]
-
-
-
-
-
array([0, 1, 2, 3])
-
-
-
-
-

where again, these default values are optional,

-
-
-
b[::]
-
-
-
-
-
array([0, 1, 2, 3])
-
-
-
-
-

and even the second : is optional

-
-
-
b[:]
-
-
-
-
-
array([0, 1, 2, 3])
-
-
-
-
-

Now to actually make our own slice, let’s select all elements from m=0 to m=2

-
-
-
b[0:2]
-
-
-
-
-
array([0, 1])
-
-
-
-
-
-

Warning

-

Slice notation is exclusive of the final index.

-
-

This means that slices will include every value up to your stop index and not this index itself, like a half-open interval [start, end). For example,

-
-
-
b[3]
-
-
-
-
-
3
-
-
-
-
-

reveals a different value than

-
-
-
b[0:3]
-
-
-
-
-
array([0, 1, 2])
-
-
-
-
-

Finally, a few more examples of this notation before reintroducing our 2-d array a.

-
-
-
b[2:]  # m=2 through the end, can leave off the number
-
-
-
-
-
array([2, 3])
-
-
-
-
-
-
-
b[:3]  # similarly, the same as our b[0:3]
-
-
-
-
-
array([0, 1, 2])
-
-
-
-
-
-
-

Multidimensional slicing

-

This entire syntax can be extended to each dimension of multidimensional arrays.

-
-
-
a
-
-
-
-
-
array([[ 0,  1,  2,  3],
-       [ 4,  5,  6,  7],
-       [ 8,  9, 10, 11]])
-
-
-
-
-

First let’s pull out rows 0 through 2, and then every : column for each of those

-
-
-
a[0:2, :]
-
-
-
-
-
array([[0, 1, 2, 3],
-       [4, 5, 6, 7]])
-
-
-
-
-

Similarly, let’s get all rows for just column 2,

-
-
-
a[:, 2]
-
-
-
-
-
array([ 2,  6, 10])
-
-
-
-
-

or just take a look at the full row :, for every second column, ::2,

-
-
-
a[:, ::2]
-
-
-
-
-
array([[ 0,  2],
-       [ 4,  6],
-       [ 8, 10]])
-
-
-
-
-

For any shape of array, you can use ... to capture full slices of every non-specified dimension. Consider the 3-D array,

-
-
-
c = a.reshape(2, 2, 3)
-c
-
-
-
-
-
array([[[ 0,  1,  2],
-        [ 3,  4,  5]],
-
-       [[ 6,  7,  8],
-        [ 9, 10, 11]]])
-
-
-
-
-
-
-
c[0, ...]
-
-
-
-
-
array([[0, 1, 2],
-       [3, 4, 5]])
-
-
-
-
-

and so this is equivalent to

-
-
-
c[0, :, :]
-
-
-
-
-
array([[0, 1, 2],
-       [3, 4, 5]])
-
-
-
-
-

for extracting every dimension across our first row. We can also flip this around,

-
-
-
c[..., -1]
-
-
-
-
-
array([[ 2,  5],
-       [ 8, 11]])
-
-
-
-
-

to investigate every preceding dimension along our the last entry of our last axis, the same as c[:, :, -1].

-
-
-
-
-

Summary

-

In this notebook we introduced NumPy and the ndarray that is so crucial to the entirety of the scientific Python community ecosystem. We created some arrays, used some of NumPy’s own mathematical functions to manipulate them, and then introduced the world of NumPy indexing and selecting for even multi-dimensional arrays.

-
-

What’s next?

-

This notebook is the gateway to nearly every other Pythia resource here. This information is crucial for understanding SciPy, pandas, xarray, and more. Continue into NumPy to explore some more intermediate and advanced topics!

-
-
-
-

Resources and references

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/numpy/numpy-broadcasting.html b/_preview/419/core/numpy/numpy-broadcasting.html deleted file mode 100644 index da03bed2e..000000000 --- a/_preview/419/core/numpy/numpy-broadcasting.html +++ /dev/null @@ -1,1846 +0,0 @@ - - - - - - - - NumPy Broadcasting — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -

NumPy Logo

-
-

NumPy Broadcasting

-
-
-

Overview

-

Before we begin, it is important to know that broadcasting is a valuable part of the power that NumPy provides. However, there’s no looking past the fact that broadcasting can be conceptually difficult to digest. This information can be helpful and very powerful, but it may be more prudent to first start learning the other label-based elements of the Python ecosystem, Pandas and Xarray. This can make understanding NumPy broadcasting easier or simpler when using real-world data. When you are ready to learn about NumPy broadcasting, this section is organized as follows:

-
    -
  1. An introduction to broadcasting

  2. -
  3. Avoiding loops with vectorization

  4. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

NumPy Basics

Necessary

Intermediate NumPy

Helpful

Conceptual guide to broadcasting

Helpful

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

Imports

-

As always, when working with NumPy, it must be imported first:

-
-
-
import numpy as np
-
-
-
-
-
-
-

Using broadcasting to implicitly loop over data

-
-

What is broadcasting?

-

Broadcasting is a useful NumPy tool that allows us to perform operations between arrays with different shapes, provided that they are compatible with each other in certain ways. To start, we can create an array below and add 5 to it:

-
-
-
a = np.array([10, 20, 30, 40])
-a + 5
-
-
-
-
-
array([15, 25, 35, 45])
-
-
-
-
-

This works even though 5 is not an array. It behaves as expected, adding 5 to each of the elements in a. This also works if 5 is an array:

-
-
-
b = np.array([5])
-a + b
-
-
-
-
-
array([15, 25, 35, 45])
-
-
-
-
-

This takes the single element in b and adds it to each of the elements in a. This won’t work for just any b, though; for instance, the following won’t work:

-
-
-
b = np.array([5, 6, 7])
-a + b
-
-
-
-
-
---------------------------------------------------------------------------
-ValueError                                Traceback (most recent call last)
-Cell In[4], line 2
-      1 b = np.array([5, 6, 7])
-----> 2 a + b
-
-ValueError: operands could not be broadcast together with shapes (4,) (3,) 
-
-
-
-
-

It does work if a and b are the same shape:

-
-
-
b = np.array([5, 5, 10, 10])
-a + b
-
-
-
-
-
array([15, 25, 40, 50])
-
-
-
-
-

What if what we really want is pairwise addition of a and b? Without broadcasting, we could accomplish this by looping:

-
-
-
b = np.array([1, 2, 3, 4, 5])
-
-
-
-
-
-
-
result = np.empty((5, 4), dtype=np.int32)
-for row, valb in enumerate(b):
-    for col, vala in enumerate(a):
-        result[row, col] = vala + valb
-result
-
-
-
-
-
array([[11, 21, 31, 41],
-       [12, 22, 32, 42],
-       [13, 23, 33, 43],
-       [14, 24, 34, 44],
-       [15, 25, 35, 45]], dtype=int32)
-
-
-
-
-

We can also do this by manually repeating the arrays to the proper shape for the result, using np.tile. This avoids the need to manually loop:

-
-
-
aa = np.tile(a, (5, 1))
-aa
-
-
-
-
-
array([[10, 20, 30, 40],
-       [10, 20, 30, 40],
-       [10, 20, 30, 40],
-       [10, 20, 30, 40],
-       [10, 20, 30, 40]])
-
-
-
-
-
-
-
# Turn b into a column array, then tile it
-bb = np.tile(b.reshape(5, 1), (1, 4))
-bb
-
-
-
-
-
array([[1, 1, 1, 1],
-       [2, 2, 2, 2],
-       [3, 3, 3, 3],
-       [4, 4, 4, 4],
-       [5, 5, 5, 5]])
-
-
-
-
-
-
-
aa + bb
-
-
-
-
-
array([[11, 21, 31, 41],
-       [12, 22, 32, 42],
-       [13, 23, 33, 43],
-       [14, 24, 34, 44],
-       [15, 25, 35, 45]])
-
-
-
-
-
-
-

Giving NumPy room for broadcasting

-

We can also do this using broadcasting, which is where NumPy implicitly repeats the array without using additional memory. With broadcasting, NumPy takes care of repeating for you, provided dimensions are “compatible”. This works as follows:

-
    -
  1. Check the number of dimensions of the arrays. If they are different, prepend dimensions of size one until the arrays are the same dimension shape.

  2. -
  3. Check if each of the dimensions are compatible. This works as follows:

  4. -
-
    -
  • Each dimension is checked.

  • -
  • If one of the arrays has a size of 1 in the checked dimension, or both arrays have the same size in the checked dimension, the check passes.

  • -
  • If all dimension checks pass, the dimensions are compatible.

  • -
-

For example, consider the following arrays:

-
-
-
a.shape
-
-
-
-
-
(4,)
-
-
-
-
-
-
-
b.shape
-
-
-
-
-
(5,)
-
-
-
-
-

Right now, these arrays both have the same number of dimensions. They both have only one dimension, but that dimension is incompatible. We can solve this by appending a dimension using np.newaxis when indexing, like this:

-
-
-
bb = b[:, np.newaxis]
-bb.shape
-
-
-
-
-
(5, 1)
-
-
-
-
-
-
-
a + bb
-
-
-
-
-
array([[11, 21, 31, 41],
-       [12, 22, 32, 42],
-       [13, 23, 33, 43],
-       [14, 24, 34, 44],
-       [15, 25, 35, 45]])
-
-
-
-
-
-
-
(a + bb).shape
-
-
-
-
-
(5, 4)
-
-
-
-
-

We can also make the code more succinct by performing the newaxis and addition operations in a single line, like this:

-
-
-
a + b[:, np.newaxis]
-
-
-
-
-
array([[11, 21, 31, 41],
-       [12, 22, 32, 42],
-       [13, 23, 33, 43],
-       [14, 24, 34, 44],
-       [15, 25, 35, 45]])
-
-
-
-
-
-
-

Extending to higher dimensions

-

The same broadcasting ability and rules also apply for arrays of higher dimensions. Consider the following arrays x, y, and z, which are all different dimensions. We can use newaxis and broadcasting to perform \(x^2 + y^2 + z^2\):

-
-
-
x = np.array([1, 2])
-y = np.array([3, 4, 5])
-z = np.array([6, 7, 8, 9])
-
-
-
-
-

First, we extend the x array using newaxis, and then square it. Then, we square y, and broadcast it onto the extended x array:

-
-
-
d_2d = x[:, np.newaxis] ** 2 + y**2
-
-
-
-
-
-
-
d_2d.shape
-
-
-
-
-
(2, 3)
-
-
-
-
-

Finally, we further extend this new 2-D array to a 3-D array using newaxis, square the z array, and then broadcast z onto the newly extended array:

-
-
-
d_3d = d_2d[..., np.newaxis] + z**2
-
-
-
-
-
-
-
d_3d.shape
-
-
-
-
-
(2, 3, 4)
-
-
-
-
-

As described above, we can also perform these operations in a single line of code, like this:

-
-
-
h = x[:, np.newaxis, np.newaxis] ** 2 + y[np.newaxis, :, np.newaxis] ** 2 + z**2
-
-
-
-
-

We can use the shape method to see the shape of the array created by the single line of code above. As you can see, it matches the shape of the array created by the multi-line process above:

-
-
-
h.shape
-
-
-
-
-
(2, 3, 4)
-
-
-
-
-

We can also use the all method to confirm that both arrays contain the same data:

-
-
-
np.all(h == d_3d)
-
-
-
-
-
True
-
-
-
-
-

Broadcasting is often useful when you want to do calculations with coordinate values, which are often given as 1-D arrays corresponding to positions along a particular array dimension. For example, we can use broadcasting to help with taking range and azimuth values for radar data (1-D separable polar coordinates) and converting to x,y pairs relative to the radar location.

-

Given the 3-D temperature field and 1-D pressure coordinates below, let’s calculate \(T * exp(P / 1000)\). We will need to use broadcasting to make the arrays compatible. The following code demonstrates how to use newaxis and broadcasting to perform this calculation:

-
-
-
pressure = np.array([1000, 850, 500, 300])
-temps = np.linspace(20, 30, 24).reshape(4, 3, 2)
-pressure.shape, temps.shape
-
-
-
-
-
((4,), (4, 3, 2))
-
-
-
-
-
-
-
pressure[:, np.newaxis, np.newaxis].shape
-
-
-
-
-
(4, 1, 1)
-
-
-
-
-
-
-
temps * np.exp(pressure[:, np.newaxis, np.newaxis] / 1000)
-
-
-
-
-
array([[[54.36563657, 55.54749823],
-        [56.7293599 , 57.91122156],
-        [59.09308323, 60.27494489]],
-
-       [[52.89636361, 53.91360137],
-        [54.93083913, 55.94807689],
-        [56.96531466, 57.98255242]],
-
-       [[41.57644944, 42.29328477],
-        [43.01012011, 43.72695544],
-        [44.44379078, 45.16062611]],
-
-       [[37.56128856, 38.14818369],
-        [38.73507883, 39.32197396],
-        [39.90886909, 40.49576423]]])
-
-
-
-
-
-
-
-

Vectorize calculations to avoid explicit loops

-

When working with arrays of data, loops over the individual array elements is a fact of life. However, for improved runtime performance, it is important to avoid performing these loops in Python as much as possible, and let NumPy handle the looping for you. Avoiding these loops frequently, but not always, results in shorter and clearer code as well.

-
-

Look ahead/behind

-

One common pattern for vectorizing is in converting loops that work over the current point, in addition to the previous point and/or the next point. This comes up when doing finite-difference calculations, e.g., approximating derivatives:

-
-\[\begin{equation*} -f'(x) = f_{i+1} - f_{i} -\end{equation*}\]
-
-
-
a = np.linspace(0, 20, 6)
-a
-
-
-
-
-
array([ 0.,  4.,  8., 12., 16., 20.])
-
-
-
-
-

We can calculate the forward difference for this array using a manual loop, like this:

-
-
-
d = np.zeros(a.size - 1)
-for i in range(len(a) - 1):
-    d[i] = a[i + 1] - a[i]
-d
-
-
-
-
-
array([4., 4., 4., 4., 4.])
-
-
-
-
-

It would be nice to express this calculation without a loop, if possible. To see how to go about this, let’s consider the values that are involved in calculating d[i]; in other words, the values a[i+1] and a[i]. The values over the loop iterations are:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

i

a[i+1]

a[i]

0

4

0

1

8

4

2

12

8

3

16

12

4

20

16

-

We can then express the series of values for a[i+1] as follows:

-
-
-
a[1:]
-
-
-
-
-
array([ 4.,  8., 12., 16., 20.])
-
-
-
-
-

We can also express the series of values for a[i] as follows:

-
-
-
a[:-1]
-
-
-
-
-
array([ 0.,  4.,  8., 12., 16.])
-
-
-
-
-

This means that we can express the forward difference using the following statement:

-
-
-
a[1:] - a[:-1]
-
-
-
-
-
array([4., 4., 4., 4., 4.])
-
-
-
-
-

It should be noted that using slices in this way returns only a view on the original array. In other words, you can use the slices to modify the original data, either intentionally or accidentally. Also, this is a quick operation that does not involve a copy and does not bloat memory usage.

-
-

2nd Derivative

-

A finite-difference estimate of the 2nd derivative is given by the following equation (ignoring \(\Delta x\)):

-
-\[\begin{equation*} -f''(x) = 2 -f_i - f_{i+1} - f_{i-1} -\end{equation*}\]
-

Let’s write some vectorized code to calculate this finite difference for a, using slices. Analyze the code below, and compare the result to the values you would expect to see from the 2nd derivative of a.

-
-
-
2 * a[1:-1] - a[:-2] - a[2:]
-
-
-
-
-
array([0., 0., 0., 0.])
-
-
-
-
-
-
-
-

Blocking

-

Another application that can become more efficient using vectorization is operating on blocks of data. Let’s start by creating some temperature data (rounding to make it easier to see and recognize the values):

-
-
-
temps = np.round(20 + np.random.randn(10) * 5, 1)
-temps
-
-
-
-
-
array([20. , 21.4, 13.7, 25.4, 28.2, 28.9, 14. , 23.4, 16. , 17.9])
-
-
-
-
-

Let’s start by writing a loop to take a 3-point running mean of the data. We’ll do this by iterating over all points in the array and averaging the 3 points centered on each point. We’ll simplify the problem by avoiding dealing with the cases at the edges of the array:

-
-
-
avg = np.zeros_like(temps)
-for i in range(1, len(temps) - 1):
-    sub = temps[i - 1 : i + 2]
-    avg[i] = sub.mean()
-
-
-
-
-
-
-
avg
-
-
-
-
-
array([ 0.        , 18.36666667, 20.16666667, 22.43333333, 27.5       ,
-       23.7       , 22.1       , 17.8       , 19.1       ,  0.        ])
-
-
-
-
-

As with the case of doing finite differences, we can express this using slices of the original array instead of loops:

-
-
-
# i - 1            i          i + 1
-(temps[:-2] + temps[1:-1] + temps[2:]) / 3
-
-
-
-
-
array([18.36666667, 20.16666667, 22.43333333, 27.5       , 23.7       ,
-       22.1       , 17.8       , 19.1       ])
-
-
-
-
-

Another option to solve this type of problem is to use the powerful NumPy tool as_strided instead of slicing. This tool can result in some odd behavior, so take care when using it. However, the trade-off is that the as_strided tool can be used to perform powerful operations. What we’re doing here is altering how NumPy is interpreting the values in the memory that underpins the array. Take this array, for example:

-
-
-
temps
-
-
-
-
-
array([20. , 21.4, 13.7, 25.4, 28.2, 28.9, 14. , 23.4, 16. , 17.9])
-
-
-
-
-

Using as_strided, we can create a view of this array with a new, bigger shape, with rows made up of overlapping values. We do this by specifying a new shape of 8x3. There are 3 columns, for fitting blocks of data containing 3 values each, and 8 rows, to correspond to the 8 blocks of data of that size that are possible in the original 1-D array. We can then use the strides argument to control how NumPy walks between items in each dimension. The last item in the strides tuple simply states that the number of bytes to walk between items is just the size of an item. (Increasing this last item would skip items.) The first item says that when we go to a new element (in this example, a new row), only advance the size of a single item. This is what gives us overlapping rows. The code for these operations looks like this:

-
-
-
block_size = 3
-new_shape = (len(temps) - block_size + 1, block_size)
-bytes_per_item = temps.dtype.itemsize
-temps_strided = np.lib.stride_tricks.as_strided(
-    temps, shape=new_shape, strides=(bytes_per_item, bytes_per_item)
-)
-temps_strided
-
-
-
-
-
array([[20. , 21.4, 13.7],
-       [21.4, 13.7, 25.4],
-       [13.7, 25.4, 28.2],
-       [25.4, 28.2, 28.9],
-       [28.2, 28.9, 14. ],
-       [28.9, 14. , 23.4],
-       [14. , 23.4, 16. ],
-       [23.4, 16. , 17.9]])
-
-
-
-
-

Now that we have this view of the array with the rows representing overlapping blocks, we can operate across the rows with mean and the axis=-1 argument to get our running average:

-
-
-
temps_strided.mean(axis=-1)
-
-
-
-
-
array([18.36666667, 20.16666667, 22.43333333, 27.5       , 23.7       ,
-       22.1       , 17.8       , 19.1       ])
-
-
-
-
-

It should be noted that there are no copies going on here, so if we change a value at a single indexed location, the change actually shows up in multiple locations:

-
-
-
temps_strided[0, 2] = 2000
-temps_strided
-
-
-
-
-
array([[  20. ,   21.4, 2000. ],
-       [  21.4, 2000. ,   25.4],
-       [2000. ,   25.4,   28.2],
-       [  25.4,   28.2,   28.9],
-       [  28.2,   28.9,   14. ],
-       [  28.9,   14. ,   23.4],
-       [  14. ,   23.4,   16. ],
-       [  23.4,   16. ,   17.9]])
-
-
-
-
-
-
-

Finding the difference between min and max

-

Another operation that crops up when slicing and dicing data is trying to identify a set of indices along a particular axis, contained within a larger multidimensional array. For instance, say we have a 3-D array of temperatures, and we want to identify the location of the \(-10^oC\) isotherm within each column:

-
-
-
pressure = np.linspace(1000, 100, 25)
-temps = np.random.randn(25, 30, 40) * 3 + np.linspace(25, -100, 25).reshape(-1, 1, 1)
-
-
-
-
-

NumPy has the function argmin(), which returns the index of the minimum value. We can use this to find the minimum absolute difference between the value and -10:

-
-
-
# Using axis=0 to tell it to operate along the pressure dimension
-inds = np.argmin(np.abs(temps - -10), axis=0)
-inds
-
-
-
-
-
array([[7, 6, 6, ..., 5, 8, 6],
-       [7, 7, 6, ..., 7, 7, 7],
-       [7, 8, 7, ..., 7, 6, 7],
-       ...,
-       [7, 5, 7, ..., 7, 8, 7],
-       [7, 7, 6, ..., 8, 7, 7],
-       [7, 6, 7, ..., 7, 7, 6]])
-
-
-
-
-
-
-
inds.shape
-
-
-
-
-
(30, 40)
-
-
-
-
-

Great! We now have an array representing the index of the point closest to \(-10^oC\) in each column of data. We can use this new array as a lookup index for our pressure coordinate array to find the pressure level for each column:

-
-
-
pressure[inds]
-
-
-
-
-
array([[737.5, 775. , 775. , ..., 812.5, 700. , 775. ],
-       [737.5, 737.5, 775. , ..., 737.5, 737.5, 737.5],
-       [737.5, 700. , 737.5, ..., 737.5, 775. , 737.5],
-       ...,
-       [737.5, 812.5, 737.5, ..., 737.5, 700. , 737.5],
-       [737.5, 737.5, 775. , ..., 700. , 737.5, 737.5],
-       [737.5, 775. , 737.5, ..., 737.5, 737.5, 775. ]])
-
-
-
-
-

Now, we can try to find the closest actual temperature value using the new array:

-
-
-
temps[inds, :, :].shape
-
-
-
-
-
(30, 40, 30, 40)
-
-
-
-
-

Unfortunately, this replaced the pressure dimension (size 25) with the shape of our index array (30 x 40), giving us a 30 x 40 x 30 x 40 array. Obviously, if scientifically relevant data values were being used, this result would almost certainly make such data invalid. One solution would be to set up a loop with the ndenumerate function, like this:

-
-
-
output = np.empty(inds.shape, dtype=temps.dtype)
-for (i, j), val in np.ndenumerate(inds):
-    output[i, j] = temps[val, i, j]
-output
-
-
-
-
-
array([[-12.0099249 , -11.5870188 ,  -8.34904914, ...,  -7.67748086,
-        -14.3360727 ,  -8.87454273],
-       [-11.39487287, -10.92809205,  -9.58591346, ..., -10.66682529,
-        -14.03276029,  -7.85404487],
-       [ -9.96413698, -10.61578583, -11.83021476, ..., -11.99079356,
-         -6.17403196, -10.62560384],
-       ...,
-       [-11.81564456,  -8.55797222,  -9.39016407, ...,  -9.28325182,
-         -9.6530609 ,  -8.05937297],
-       [ -7.97122547, -11.44749909,  -5.91952686, ..., -10.903761  ,
-        -12.56282449,  -8.36869563],
-       [-10.76441131,  -9.23222462, -10.62001244, ..., -10.47387517,
-        -11.0531872 ,  -9.03600639]])
-
-
-
-
-

Of course, what we really want to do is avoid the explicit loop. Let’s temporarily simplify the problem to a single dimension. If we have a 1-D array, we can pass a 1-D array of indices (a full range), and get back the same as the original data array:

-
-
-
pressure[np.arange(pressure.size)]
-
-
-
-
-
array([1000. ,  962.5,  925. ,  887.5,  850. ,  812.5,  775. ,  737.5,
-        700. ,  662.5,  625. ,  587.5,  550. ,  512.5,  475. ,  437.5,
-        400. ,  362.5,  325. ,  287.5,  250. ,  212.5,  175. ,  137.5,
-        100. ])
-
-
-
-
-
-
-
np.all(pressure[np.arange(pressure.size)] == pressure)
-
-
-
-
-
True
-
-
-
-
-

We can use this to select all the indices on the other dimensions of our temperature array. We will also need to use the magic of broadcasting to combine arrays of indices across dimensions.

-

This can be written as a vectorized solution. For example:

-
-
-
y_inds = np.arange(temps.shape[1])[:, np.newaxis]
-x_inds = np.arange(temps.shape[2])
-temps[inds, y_inds, x_inds]
-
-
-
-
-
array([[-12.0099249 , -11.5870188 ,  -8.34904914, ...,  -7.67748086,
-        -14.3360727 ,  -8.87454273],
-       [-11.39487287, -10.92809205,  -9.58591346, ..., -10.66682529,
-        -14.03276029,  -7.85404487],
-       [ -9.96413698, -10.61578583, -11.83021476, ..., -11.99079356,
-         -6.17403196, -10.62560384],
-       ...,
-       [-11.81564456,  -8.55797222,  -9.39016407, ...,  -9.28325182,
-         -9.6530609 ,  -8.05937297],
-       [ -7.97122547, -11.44749909,  -5.91952686, ..., -10.903761  ,
-        -12.56282449,  -8.36869563],
-       [-10.76441131,  -9.23222462, -10.62001244, ..., -10.47387517,
-        -11.0531872 ,  -9.03600639]])
-
-
-
-
-

Now, we can use this new array to find, for example, the relative humidity at the \(-10^oC\) isotherm:

-
-
-
np.all(output == temps[inds, y_inds, x_inds])
-
-
-
-
-
True
-
-
-
-
-
-
-
-
-

Summary

-

We’ve previewed some advanced NumPy capabilities, with a focus on vectorization; in other words, using clever broadcasting and data windowing techniques to enhance the speed and readability of our calculation code. By making use of vectorization, you can reduce explicit construction of loops in your code, and improve speed of calculation throughout the execution of such code.

-
-

What’s next

-

This is an advanced NumPy topic; however, it is important to learn this topic in order to design calculation code that maximizes scalability and speed. If you would like to explore this topic further, please review the links below. We also suggest diving into label-based indexing and subsetting with Pandas and Xarray, where some of this broadcasting can be simplified, or have added context.

-
-
-
-

Resources and references

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/overview.html b/_preview/419/core/overview.html deleted file mode 100644 index a57da13fb..000000000 --- a/_preview/419/core/overview.html +++ /dev/null @@ -1,994 +0,0 @@ - - - - - - - - Overview — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Overview

-

A group of programs that works in tandem to produce a result or achieve a common goal is often referred to as a software stack. -This page gives an overview of the Python geoscience stack. -Scroll to the end of the page for cross-referenced tutorial material for several of the packages in the stack. -We suggest that new users start with the Foundational Skills section in order to get the most out of these tutorials.

-
-

Core libraries

-

Most geoscience data analysis involves working with numerical arrays. -The default library for dealing with numerical arrays in Python is NumPy. -It has some built in functions for calculating very simple statistics -(e.g. maximum, mean, standard deviation), -but for more complex analysis -(e.g. interpolation, integration, linear algebra) -the SciPy library is the default. -If you’re dealing with particularly large arrays, -Dask works with the existing Python ecosystem -(including NumPy) to scale your analysis -to multi-core machines and/or distributed clusters (i.e. parallel processing).

-

Another common feature of geo-data science is time series analysis. -The Python standard library comes with a datetime -package for manipulating dates and times. -NumPy also includes a datetime64 -module for efficient vectorized datetime operations -and the cftime library -is useful for dealing with non-standard calendars.

-

When it comes to data visualization, -the default library is Matplotlib. -As you can see at the Matplotlib gallery, -this library is great for any simple (e.g. bar charts, contour plots, line graphs), -static (e.g. .png, .eps, .pdf) plots. -The Cartopy library -provides additional plotting functionality for common geographic map projections.

-
-
-

High-level libraries

-

While pretty much all data analysis and visualization tasks -could be achieved with a combination of the core libraries, -their flexible, all-purpose nature means relatively common/simple tasks -can often require quite a bit of work (i.e. many lines of code). -To make things more efficient for data scientists, -the scientific Python community has therefore built a number of libraries on top of the core stack. -These high-levels libraries aren’t as flexible -– they can’t do everything like the core stack can – -but they can do common tasks with far less effort.

-

The most popular high-level data science library is undoubtedly Pandas. -The key advance offered by Pandas is the concept of labeled arrays. -Rather than referring to the individual elements of a data array using a numeric index -(as is required with NumPy), -the actual row and column headings can be used. -That means information from the cardiac ward on 3 July 2005 -could be obtained from a medical dataset by asking for data['cardiac'].loc['2005-07-03'], -rather than having to remember the numeric index corresponding to that ward and date. -This labeled array feature, -combined with a bunch of other features that streamline common statistical and plotting tasks -traditionally performed with SciPy, datetime and Matplotlib, -greatly simplifies the code development process (read: less lines of code).

-

One of the limitations of Pandas -is that it’s only able to handle one- or two-dimensional (i.e. tabular) data arrays. -The Xarray library was therefore created -to extend the labelled array concept to x-dimensional arrays. -Not all of the Pandas functionality is available -(which is a trade-off associated with being able to handle multi-dimensional arrays), -but the ability to refer to array elements by their actual latitude (e.g. 20 South), -longitude (e.g. 50 East), height (e.g. 500 hPa) and time (e.g. 2015-04-27), for example, -makes the Xarray data array far easier to deal with than the NumPy array. -As an added bonus, -Xarray also has built in functionality for reading/writing specific geoscience file formats -(e.g netCDF, GRIB) -and incorporates Dask under the hood to make dealing with large arrays easier.

-

You will occasionally find yourself needing to use a core library directly -(e.g. you might create a plot with Xarray and then call a specific Matplotlib -function to customise a label on that plot), -but to avoid re-inventing the wheel your first impulse should always be -to check whether a high-level library like Pandas or Xarray has the functionality you need. -Nothing would be more heartbreaking than spending hours writing your own function -using the netCDF4 library for extracting the metadata contained within a netCDF file, -for instance, -only to find that Xarray automatically keeps this information upon reading a netCDF file. -In this way, a solid working knowledge of the geoscience stack -can save you a lot of time and effort.

-
-
-

Domain-specific libraries

-

So far we’ve considered libraries that do general, -broad-scale tasks like data input/output, common statistics, visualisation, etc. -Given their large user base, -these libraries are usually written and supported by large companies/institutions -(e.g. the MetOffice supports Cartopy) -or the wider PyData community (e.g. NumPy, Pandas, Xarray). -Within each sub-discipline of the geosciences, -individuals and research groups take these general libraries -and apply them to their very specific data analysis tasks. -Increasingly, these individuals and groups -are formally packaging and releasing their code for use within their community. -For instance, Andrew Dawson (an atmospheric scientist at Oxford) -does a lot of EOF analysis and manipulation of wind data, -so he has released his eofs -and windspharm libraries -(which are able to handle data arrays from NumPy or Xarray). -Similarly, a group at the Atmospheric Radiation Measurement (ARM) Climate Research Facility -have released their Python ARM Radar Toolkit (Py-ART) -for analysing weather radar data.

-

There are too many domain specific libraries to mention here, -but online resources such as the -Python for Atmosphere and Ocean Science (PyAOS) package index -attempt to keep track of the domain-specific libraries in their field. -Also check out the Pythia Resource Gallery and try filtering by domain.

-
-
-

Tutorials

-
    -
  • NumPy: Core package for array computing, the workhorse of the Scientific Python stack

  • -
  • Matplotlib: Basic plotting

  • -
  • Cartopy: Plotting on map projections

  • -
  • Datetime: Dealing with time and calendar data

  • -
  • Pandas: Working with labeled tabular data

  • -
  • Data formats: Working with common geoscience data formats

  • -
  • Xarray: Working with gridded and labeled N-dimensional data

  • -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/pandas.html b/_preview/419/core/pandas.html deleted file mode 100644 index a66f68d25..000000000 --- a/_preview/419/core/pandas.html +++ /dev/null @@ -1,858 +0,0 @@ - - - - - - - - Pandas — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -
-

Pandas

-
-

Note

-

This content is under construction!

-
-

This section will contain tutorials on using pandas for labeled tabular data.

-
-

From the official documentation, Pandas “is a fast, powerful, flexible and easy to use open source data analysis and manipulation tool, built on top of the Python programming language.”

-

Pandas is a very powerful library for working with tabular data (e.g., spreadsheets, comma-separated-value files, or database printouts; all of these are quite common for geoscientific data). It allows us to use labels for our data; this, in turn, allows us to write expressive and robust code to manipulate the data.

-

Key features of Pandas are the abilities to read in tabular data and to slice and dice data, as well as exploratory analysis tools native to the library.

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/pandas/pandas.html b/_preview/419/core/pandas/pandas.html deleted file mode 100644 index 3bfb2e43c..000000000 --- a/_preview/419/core/pandas/pandas.html +++ /dev/null @@ -1,4625 +0,0 @@ - - - - - - - - Introduction to Pandas — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -
pandas Logo
-
-

Introduction to Pandas

-
-
-

Overview

-
    -
  1. Introduction to pandas data structures

  2. -
  3. How to slice and dice pandas dataframes and dataseries

  4. -
  5. How to use pandas for exploratory data analysis

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Python Quickstart

Necessary

Intro to dict

Numpy Basics

Necessary

-
    -
  • Time to learn: 60 minutes

  • -
-
-
-
-

Imports

-

You will often see the nickname pd used as an abbreviation for pandas in the import statement, just like numpy is often imported as np. We also import the DATASETS class from pythia_datasets, which allows us to use example datasets created for Pythia.

-
-
-
import pandas as pd
-from pythia_datasets import DATASETS
-
-
-
-
-
-
-

The pandas DataFrame

-

…is a labeled, two-dimensional columnar structure, similar to a table, spreadsheet, or the R data.frame.

-

dataframe schematic

-

The columns that make up our DataFrame can be lists, dictionaries, NumPy arrays, pandas Series, or many other data types not mentioned here. Within these columns, you can have data values of many different data types used in Python and NumPy, including text, numbers, and dates/times. The first column of a DataFrame, shown in the image above in dark gray, is uniquely referred to as an index; this column contains information characterizing each row of our DataFrame. Similar to any other column, the index can label rows by text, numbers, datetime objects, and many other data types. Datetime objects are a quite popular way to label rows.

-

For our first example using Pandas DataFrames, we start by reading in some data in comma-separated value (.csv) format. We retrieve this dataset from the Pythia DATASETS class (imported at the top of this page); however, the dataset was originally contained within the NCDC teleconnections database. This dataset contains many types of geoscientific data, including El Nino/Southern Oscillation indices. For more information on this dataset, review the description here.

-
-

Info

-

As described above, we are retrieving the datasets for these examples from Project Pythia’s custom library of example data. In order to retrieve datasets from this library, you must use the statement from pythia_datasets import DATASETS. This is shown and described in the Imports section at the top of this page. The fetch() method of the DATASETS class will automatically download the data file specified as a string argument, in this case enso_data.csv, and cache the file locally, assuming the argument corresponds to a valid Pythia example dataset. This is illustrated in the following example.

-
-
-
-
filepath = DATASETS.fetch('enso_data.csv')
-
-
-
-
-
Downloading file 'enso_data.csv' from 'https://github.com/ProjectPythia/pythia-datasets/raw/main/data/enso_data.csv' to '/home/runner/.cache/pythia-datasets'.
-
-
-
-
-

Once we have a valid path to a data file that Pandas knows how to read, we can open it, as shown in the following example:

-
-
-
df = pd.read_csv(filepath)
-
-
-
-
-

If we print out our DataFrame, it will render as text by default, in a tabular-style ASCII output, as shown in the following example. However, if you are using a Jupyter notebook, there exists a better way to print DataFrames, as described below.

-
-
-
print(df)
-
-
-
-
-
       datetime  Nino12  Nino12anom  Nino3  Nino3anom  Nino4  Nino4anom  \
-0    1982-01-01   24.29       -0.17  25.87       0.24  28.30       0.00   
-1    1982-02-01   25.49       -0.58  26.38       0.01  28.21       0.11   
-2    1982-03-01   25.21       -1.31  26.98      -0.16  28.41       0.22   
-3    1982-04-01   24.50       -0.97  27.68       0.18  28.92       0.42   
-4    1982-05-01   23.97       -0.23  27.79       0.71  29.49       0.70   
-..          ...     ...         ...    ...        ...    ...        ...   
-467  2020-12-01   22.16       -0.60  24.38      -0.83  27.65      -0.95   
-468  2021-01-01   23.89       -0.64  25.06      -0.55  27.10      -1.25   
-469  2021-02-01   25.55       -0.66  25.80      -0.57  27.20      -1.00   
-470  2021-03-01   26.48       -0.26  26.80      -0.39  27.79      -0.55   
-471  2021-04-01   24.89       -0.80  26.96      -0.65  28.47      -0.21   
-
-     Nino34  Nino34anom  
-0     26.72        0.15  
-1     26.70       -0.02  
-2     27.20       -0.02  
-3     28.02        0.24  
-4     28.54        0.69  
-..      ...         ...  
-467   25.53       -1.12  
-468   25.58       -0.99  
-469   25.81       -0.92  
-470   26.75       -0.51  
-471   27.40       -0.49  
-
-[472 rows x 9 columns]
-
-
-
-
-

As described above, there is a better way to print Pandas DataFrames. If you are using a Jupyter notebook, you can run a code cell containing the DataFrame object name, by itself, and it will display a nicely rendered table, as shown below.

-
-
-
df
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
datetimeNino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
01982-01-0124.29-0.1725.870.2428.300.0026.720.15
11982-02-0125.49-0.5826.380.0128.210.1126.70-0.02
21982-03-0125.21-1.3126.98-0.1628.410.2227.20-0.02
31982-04-0124.50-0.9727.680.1828.920.4228.020.24
41982-05-0123.97-0.2327.790.7129.490.7028.540.69
..............................
4672020-12-0122.16-0.6024.38-0.8327.65-0.9525.53-1.12
4682021-01-0123.89-0.6425.06-0.5527.10-1.2525.58-0.99
4692021-02-0125.55-0.6625.80-0.5727.20-1.0025.81-0.92
4702021-03-0126.48-0.2626.80-0.3927.79-0.5526.75-0.51
4712021-04-0124.89-0.8026.96-0.6528.47-0.2127.40-0.49
-

472 rows × 9 columns

-
-
-

The DataFrame index, as described above, contains information characterizing rows; each row has a unique ID value, which is displayed in the index column. By default, the IDs for rows in a DataFrame are represented as sequential integers, which start at 0.

-
-
-
df.index
-
-
-
-
-
RangeIndex(start=0, stop=472, step=1)
-
-
-
-
-

At the moment, the index column of our DataFrame is not very helpful for humans. However, Pandas has clever ways to make index columns more human-readable. The next example demonstrates how to use optional keyword arguments to convert DataFrame index IDs to a human-friendly datetime format.

-
-
-
df = pd.read_csv(filepath, index_col=0, parse_dates=True)
-
-df
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
datetime
1982-01-0124.29-0.1725.870.2428.300.0026.720.15
1982-02-0125.49-0.5826.380.0128.210.1126.70-0.02
1982-03-0125.21-1.3126.98-0.1628.410.2227.20-0.02
1982-04-0124.50-0.9727.680.1828.920.4228.020.24
1982-05-0123.97-0.2327.790.7129.490.7028.540.69
...........................
2020-12-0122.16-0.6024.38-0.8327.65-0.9525.53-1.12
2021-01-0123.89-0.6425.06-0.5527.10-1.2525.58-0.99
2021-02-0125.55-0.6625.80-0.5727.20-1.0025.81-0.92
2021-03-0126.48-0.2626.80-0.3927.79-0.5526.75-0.51
2021-04-0124.89-0.8026.96-0.6528.47-0.2127.40-0.49
-

472 rows × 8 columns

-
-
-
-
-
df.index
-
-
-
-
-
DatetimeIndex(['1982-01-01', '1982-02-01', '1982-03-01', '1982-04-01',
-               '1982-05-01', '1982-06-01', '1982-07-01', '1982-08-01',
-               '1982-09-01', '1982-10-01',
-               ...
-               '2020-07-01', '2020-08-01', '2020-09-01', '2020-10-01',
-               '2020-11-01', '2020-12-01', '2021-01-01', '2021-02-01',
-               '2021-03-01', '2021-04-01'],
-              dtype='datetime64[ns]', name='datetime', length=472, freq=None)
-
-
-
-
-

Each of our data rows is now helpfully labeled by a datetime-object-like index value; this means that we can now easily identify data values not only by named columns, but also by date labels on rows. This is a sneak preview of the DatetimeIndex functionality of Pandas; this functionality enables a large portion of Pandas’ timeseries-related usage. Don’t worry; DatetimeIndex will be discussed in full detail later on this page. In the meantime, let’s look at the columns of data read in from the .csv file:

-
-
-
df.columns
-
-
-
-
-
Index(['Nino12', 'Nino12anom', 'Nino3', 'Nino3anom', 'Nino4', 'Nino4anom',
-       'Nino34', 'Nino34anom'],
-      dtype='object')
-
-
-
-
-
-
-

The pandas Series

-

…is essentially any one of the columns of our DataFrame. A Series also includes the index column from the source DataFrame, in order to provide a label for each value in the Series.

-

pandas Series

-

The pandas Series is a fast and capable 1-dimensional array of nearly any data type we could want, and it can behave very similarly to a NumPy ndarray or a Python dict. You can take a look at any of the Series that make up your DataFrame, either by using its column name and the Python dict notation, or by using dot-shorthand with the column name:

-
-
-
df["Nino34"]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-              ...  
-2020-12-01    25.53
-2021-01-01    25.58
-2021-02-01    25.81
-2021-03-01    26.75
-2021-04-01    27.40
-Name: Nino34, Length: 472, dtype: float64
-
-
-
-
-
-Tip: You can also use the dot notation illustrated below to specify a column name, but this syntax is mostly provided for convenience. For the most part, this notation is interchangeable with the dictionary notation; however, if the column name is not a valid Python identifier (e.g., it starts with a number or space), you cannot use dot notation.
-
-
df.Nino34
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-              ...  
-2020-12-01    25.53
-2021-01-01    25.58
-2021-02-01    25.81
-2021-03-01    26.75
-2021-04-01    27.40
-Name: Nino34, Length: 472, dtype: float64
-
-
-
-
-
-
-

Slicing and Dicing the DataFrame and Series

-

In this section, we will expand on topics covered in the previous sections on this page. One of the most important concepts to learn about Pandas is that it allows you to access anything by its associated label, regardless of data organization structure.

-
-

Indexing a Series

-

As a review of previous examples, we’ll start our next example by pulling a Series out of our DataFrame using its column label.

-
-
-
nino34_series = df["Nino34"]
-
-nino34_series
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-              ...  
-2020-12-01    25.53
-2021-01-01    25.58
-2021-02-01    25.81
-2021-03-01    26.75
-2021-04-01    27.40
-Name: Nino34, Length: 472, dtype: float64
-
-
-
-
-

You can use syntax similar to that of NumPy ndarrays to index, select, and subset with Pandas Series, as shown in this example:

-
-
-
nino34_series[3]
-
-
-
-
-
28.02
-
-
-
-
-

You can also use labels alongside Python dictionary syntax to perform the same operations:

-
-
-
nino34_series["1982-04-01"]
-
-
-
-
-
28.02
-
-
-
-
-

You can probably figure out some ways to extend these indexing methods, as shown in the following examples:

-
-
-
nino34_series[0:12]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-1982-06-01    28.75
-1982-07-01    28.10
-1982-08-01    27.93
-1982-09-01    28.11
-1982-10-01    28.64
-1982-11-01    28.81
-1982-12-01    29.21
-Name: Nino34, dtype: float64
-
-
-
-
-
-

Info

-

Index-based slices are exclusive of the final value, similar to Python’s usual indexing rules.

-
-

However, there are many more ways to index a Series. The following example shows a powerful and useful indexing method:

-
-
-
nino34_series["1982-01-01":"1982-12-01"]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-1982-06-01    28.75
-1982-07-01    28.10
-1982-08-01    27.93
-1982-09-01    28.11
-1982-10-01    28.64
-1982-11-01    28.81
-1982-12-01    29.21
-Name: Nino34, dtype: float64
-
-
-
-
-

This is an example of label-based slicing. With label-based slicing, Pandas will automatically find a range of values based on the labels you specify.

-
-

Info

-

As opposed to index-based slices, label-based slices are inclusive of the final value.

-
-

If you already have some knowledge of xarray, you will quite likely know how to create slice objects by hand. This can also be used in pandas, as shown below. If you are completely unfamiliar with xarray, it will be covered on a later Pythia tutorial page.

-
-
-
nino34_series[slice("1982-01-01", "1982-12-01")]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-1982-06-01    28.75
-1982-07-01    28.10
-1982-08-01    27.93
-1982-09-01    28.11
-1982-10-01    28.64
-1982-11-01    28.81
-1982-12-01    29.21
-Name: Nino34, dtype: float64
-
-
-
-
-
-
-

Using .iloc and .loc to index

-

In this section, we introduce ways to access data that are preferred by Pandas over the methods listed above. When accessing by label, it is preferred to use the .loc method, and when accessing by index, the .iloc method is preferred. These methods behave similarly to the notation introduced above, but provide more speed, security, and rigor in your value selection. Using these methods can also help you avoid chained assignment warnings generated by pandas.

-
-
-
nino34_series.iloc[3]
-
-
-
-
-
28.02
-
-
-
-
-
-
-
nino34_series.iloc[0:12]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-1982-06-01    28.75
-1982-07-01    28.10
-1982-08-01    27.93
-1982-09-01    28.11
-1982-10-01    28.64
-1982-11-01    28.81
-1982-12-01    29.21
-Name: Nino34, dtype: float64
-
-
-
-
-
-
-
nino34_series.loc["1982-04-01"]
-
-
-
-
-
28.02
-
-
-
-
-
-
-
nino34_series.loc["1982-01-01":"1982-12-01"]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-1982-06-01    28.75
-1982-07-01    28.10
-1982-08-01    27.93
-1982-09-01    28.11
-1982-10-01    28.64
-1982-11-01    28.81
-1982-12-01    29.21
-Name: Nino34, dtype: float64
-
-
-
-
-
-
-

Extending to the DataFrame

-

These subsetting capabilities can also be used in a full DataFrame; however, if you use the same syntax, there are issues, as shown below:

-
-
-
df["1982-01-01"]
-
-
-
-
-
---------------------------------------------------------------------------
-KeyError                                  Traceback (most recent call last)
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/core/indexes/base.py:3653, in Index.get_loc(self, key)
-   3652 try:
--> 3653     return self._engine.get_loc(casted_key)
-   3654 except KeyError as err:
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/_libs/index.pyx:147, in pandas._libs.index.IndexEngine.get_loc()
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/_libs/index.pyx:176, in pandas._libs.index.IndexEngine.get_loc()
-
-File pandas/_libs/hashtable_class_helper.pxi:7080, in pandas._libs.hashtable.PyObjectHashTable.get_item()
-
-File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()
-
-KeyError: '1982-01-01'
-
-The above exception was the direct cause of the following exception:
-
-KeyError                                  Traceback (most recent call last)
-Cell In[22], line 1
-----> 1 df["1982-01-01"]
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/core/frame.py:3761, in DataFrame.__getitem__(self, key)
-   3759 if self.columns.nlevels > 1:
-   3760     return self._getitem_multilevel(key)
--> 3761 indexer = self.columns.get_loc(key)
-   3762 if is_integer(indexer):
-   3763     indexer = [indexer]
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/core/indexes/base.py:3655, in Index.get_loc(self, key)
-   3653     return self._engine.get_loc(casted_key)
-   3654 except KeyError as err:
--> 3655     raise KeyError(key) from err
-   3656 except TypeError:
-   3657     # If we have a listlike key, _check_indexing_error will raise
-   3658     #  InvalidIndexError. Otherwise we fall through and re-raise
-   3659     #  the TypeError.
-   3660     self._check_indexing_error(key)
-
-KeyError: '1982-01-01'
-
-
-
-
-
-

Danger

-

Attempting to use Series subsetting with a DataFrame can crash your program. A proper way to subset a DataFrame is shown below.

-
-

When indexing a DataFrame, pandas will not assume as readily the intention of your code. In this case, using a row label by itself will not work; with DataFrames, labels are used for identifying columns.

-
-
-
df["Nino34"]
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-              ...  
-2020-12-01    25.53
-2021-01-01    25.58
-2021-02-01    25.81
-2021-03-01    26.75
-2021-04-01    27.40
-Name: Nino34, Length: 472, dtype: float64
-
-
-
-
-

As shown below, you also cannot subset columns in a DataFrame using integer indices:

-
-
-
df[0]
-
-
-
-
-
---------------------------------------------------------------------------
-KeyError                                  Traceback (most recent call last)
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/core/indexes/base.py:3653, in Index.get_loc(self, key)
-   3652 try:
--> 3653     return self._engine.get_loc(casted_key)
-   3654 except KeyError as err:
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/_libs/index.pyx:147, in pandas._libs.index.IndexEngine.get_loc()
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/_libs/index.pyx:176, in pandas._libs.index.IndexEngine.get_loc()
-
-File pandas/_libs/hashtable_class_helper.pxi:7080, in pandas._libs.hashtable.PyObjectHashTable.get_item()
-
-File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()
-
-KeyError: 0
-
-The above exception was the direct cause of the following exception:
-
-KeyError                                  Traceback (most recent call last)
-Cell In[24], line 1
-----> 1 df[0]
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/core/frame.py:3761, in DataFrame.__getitem__(self, key)
-   3759 if self.columns.nlevels > 1:
-   3760     return self._getitem_multilevel(key)
--> 3761 indexer = self.columns.get_loc(key)
-   3762 if is_integer(indexer):
-   3763     indexer = [indexer]
-
-File /usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/pandas/core/indexes/base.py:3655, in Index.get_loc(self, key)
-   3653     return self._engine.get_loc(casted_key)
-   3654 except KeyError as err:
--> 3655     raise KeyError(key) from err
-   3656 except TypeError:
-   3657     # If we have a listlike key, _check_indexing_error will raise
-   3658     #  InvalidIndexError. Otherwise we fall through and re-raise
-   3659     #  the TypeError.
-   3660     self._check_indexing_error(key)
-
-KeyError: 0
-
-
-
-
-

From earlier examples, we know that we can use an index or label with a DataFrame to pull out a column as a Series, and we know that we can use an index or label with a Series to pull out a single value. Therefore, by chaining brackets, we can pull any individual data value out of the DataFrame.

-
-
-
df["Nino34"]["1982-04-01"]
-
-
-
-
-
28.02
-
-
-
-
-
-
-
df["Nino34"][3]
-
-
-
-
-
28.02
-
-
-
-
-

However, subsetting data using this chained-bracket technique is not preferred by Pandas. As described above, Pandas prefers us to use the .loc and .iloc methods for subsetting. In addition, these methods provide a clearer, more efficient way to extract specific data from a DataFrame, as illustrated below:

-
-
-
df.loc["1982-04-01", "Nino34"]
-
-
-
-
-
28.02
-
-
-
-
-
-

Info

-

When using this syntax to pull individual data values from a DataFrame, make sure to list the row first, and then the column.

-
-

The .loc and .iloc methods also allow us to pull entire rows out of a DataFrame, as shown in these examples:

-
-
-
df.loc["1982-04-01"]
-
-
-
-
-
Nino12        24.50
-Nino12anom    -0.97
-Nino3         27.68
-Nino3anom      0.18
-Nino4         28.92
-Nino4anom      0.42
-Nino34        28.02
-Nino34anom     0.24
-Name: 1982-04-01 00:00:00, dtype: float64
-
-
-
-
-
-
-
df.loc["1982-01-01":"1982-12-01"]
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
datetime
1982-01-0124.29-0.1725.870.2428.300.0026.720.15
1982-02-0125.49-0.5826.380.0128.210.1126.70-0.02
1982-03-0125.21-1.3126.98-0.1628.410.2227.20-0.02
1982-04-0124.50-0.9727.680.1828.920.4228.020.24
1982-05-0123.97-0.2327.790.7129.490.7028.540.69
1982-06-0122.890.0727.461.0329.760.9228.751.10
1982-07-0122.470.8726.440.8229.380.5828.100.88
1982-08-0121.751.1026.151.1629.040.3627.931.11
1982-09-0121.801.4426.521.6729.160.4728.111.39
1982-10-0122.942.1227.112.1929.380.7228.641.95
1982-11-0124.593.0027.622.6429.230.6028.812.16
1982-12-0126.133.3428.393.2529.150.6629.212.64
-
-
-
-
-
df.iloc[3]
-
-
-
-
-
Nino12        24.50
-Nino12anom    -0.97
-Nino3         27.68
-Nino3anom      0.18
-Nino4         28.92
-Nino4anom      0.42
-Nino34        28.02
-Nino34anom     0.24
-Name: 1982-04-01 00:00:00, dtype: float64
-
-
-
-
-
-
-
df.iloc[0:12]
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
datetime
1982-01-0124.29-0.1725.870.2428.300.0026.720.15
1982-02-0125.49-0.5826.380.0128.210.1126.70-0.02
1982-03-0125.21-1.3126.98-0.1628.410.2227.20-0.02
1982-04-0124.50-0.9727.680.1828.920.4228.020.24
1982-05-0123.97-0.2327.790.7129.490.7028.540.69
1982-06-0122.890.0727.461.0329.760.9228.751.10
1982-07-0122.470.8726.440.8229.380.5828.100.88
1982-08-0121.751.1026.151.1629.040.3627.931.11
1982-09-0121.801.4426.521.6729.160.4728.111.39
1982-10-0122.942.1227.112.1929.380.7228.641.95
1982-11-0124.593.0027.622.6429.230.6028.812.16
1982-12-0126.133.3428.393.2529.150.6629.212.64
-
-
-

In the next example, we illustrate how you can use slices of rows and lists of columns to create a smaller DataFrame out of an existing DataFrame:

-
-
-
df.loc[
-    "1982-01-01":"1982-12-01",  # slice of rows
-    ["Nino12", "Nino3", "Nino4", "Nino34"],  # list of columns
-]
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino3Nino4Nino34
datetime
1982-01-0124.2925.8728.3026.72
1982-02-0125.4926.3828.2126.70
1982-03-0125.2126.9828.4127.20
1982-04-0124.5027.6828.9228.02
1982-05-0123.9727.7929.4928.54
1982-06-0122.8927.4629.7628.75
1982-07-0122.4726.4429.3828.10
1982-08-0121.7526.1529.0427.93
1982-09-0121.8026.5229.1628.11
1982-10-0122.9427.1129.3828.64
1982-11-0124.5927.6229.2328.81
1982-12-0126.1328.3929.1529.21
-
-
-
-

Info

-

There are certain limitations to these subsetting techniques. For more information on these limitations, as well as a comparison of DataFrame and Series indexing methods, see the Pandas indexing documentation.

-
-
-
-
-

Exploratory Data Analysis

-
-

Get a Quick Look at the Beginning/End of your DataFrame

-

Pandas also gives you a few shortcuts to quickly investigate entire DataFrames. The head method shows the first five rows of a DataFrame, and the tail method shows the last five rows of a DataFrame.

-
-
-
df.head()
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
datetime
1982-01-0124.29-0.1725.870.2428.300.0026.720.15
1982-02-0125.49-0.5826.380.0128.210.1126.70-0.02
1982-03-0125.21-1.3126.98-0.1628.410.2227.20-0.02
1982-04-0124.50-0.9727.680.1828.920.4228.020.24
1982-05-0123.97-0.2327.790.7129.490.7028.540.69
-
-
-
-
-
df.tail()
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
datetime
2020-12-0122.16-0.6024.38-0.8327.65-0.9525.53-1.12
2021-01-0123.89-0.6425.06-0.5527.10-1.2525.58-0.99
2021-02-0125.55-0.6625.80-0.5727.20-1.0025.81-0.92
2021-03-0126.48-0.2626.80-0.3927.79-0.5526.75-0.51
2021-04-0124.89-0.8026.96-0.6528.47-0.2127.40-0.49
-
-
-
-
-

Quick Plots of Your Data

-

A good way to explore your data is by making a simple plot. Pandas contains its own plot method; this allows us to plot Pandas series without needing matplotlib. In this example, we plot the Nino34 series of our df DataFrame in this way:

-
-
-
df.Nino34.plot();
-
-
-
-
-../../_images/ae4954a2e3dafba65da6ea4c9d0c47ef4dd166e276c3167ffd19b32a1b0f3b23.png -
-
-

Before, we called .plot(), which generated a single line plot. Line plots can be helpful for understanding some types of data, but there are other types of data that can be better understood with different plot types. For example, if your data values form a distribution, you can better understand them using a histogram plot.

-

The code for plotting histogram data differs in two ways from the code above for the line plot. First, two series are being used from the DataFrame instead of one. Second, after calling the plot method, we call an additional method called hist, which converts the plot into a histogram.

-
-
-
df[['Nino12', 'Nino34']].plot.hist();
-
-
-
-
-../../_images/7e19d5495940c0adbdd29975be3dff8c8b23bd5881bdca80a1fe0c559d828309.png -
-
-

The histogram plot helped us better understand our data; there are clear differences in the distributions. To even better understand this type of data, it may also be helpful to create a box plot. This can be done using the same line of code, with one change: we call the box method instead of hist.

-
-
-
df[['Nino12', 'Nino34']].plot.box();
-
-
-
-
-../../_images/a279d1c0e41635648d130b880f476c12b4bd699e2311f0f1db1e4c0fb29853e9.png -
-
-

Just like the histogram plot, this box plot indicates a clear difference in the distributions. Using multiple types of plot in this way can be useful for verifying large datasets. The pandas plotting methods are capable of creating many different types of plots. To see how to use the plotting methods to generate each type of plot, please review the pandas plot documentation.

-
-

Customize your Plot

-

The pandas plotting methods are, in fact, wrappers for similar methods in matplotlib. This means that you can customize pandas plots by including keyword arguments to the plotting methods. These keyword arguments, for the most part, are equivalent to their matplotlib counterparts.

-
-
-
df.Nino34.plot(
-    color='black',
-    linewidth=2,
-    xlabel='Year',
-    ylabel='ENSO34 Index (degC)',
-    figsize=(8, 6),
-);
-
-
-
-
-../../_images/6dc37d2c1d495e52cb2d6c95854edff74ccf3bc1f5b4e25b17389e1965f48617.png -
-
-

Although plotting data can provide a clear visual picture of data values, sometimes a more quantitative look at data is warranted. As elaborated on in the next section, this can be achieved using the describe method. The describe method is called on the entire DataFrame, and returns various summarized statistics for each column in the DataFrame.

-
-
-
-

Basic Statistics

-

We can garner statistics for a DataFrame by using the describe method. When this method is called on a DataFrame, a set of statistics is returned in tabular format. The columns match those of the DataFrame, and the rows indicate different statistics, such as minimum.

-
-
-
df.describe()
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
count472.000000472.000000472.000000472.000000472.000000472.000000472.000000472.000000
mean23.2096190.05972525.9365680.03942828.6250640.06381427.0767800.034894
std2.4315221.1575901.3496210.9654640.7554220.7094011.0630040.947936
min18.570000-2.10000023.030000-2.07000026.430000-1.87000024.270000-2.380000
25%21.152500-0.71250024.850000-0.60000028.140000-0.43000026.330000-0.572500
50%22.980000-0.16000025.885000-0.11500028.7600000.20500027.1000000.015000
75%25.3225000.51500026.9625000.51250029.1900000.63000027.7925000.565000
max29.1500004.62000029.1400003.62000030.3000001.67000029.6000002.950000
-
-
-

You can also view specific statistics using corresponding methods. In this example, we look at the mean values in the entire DataFrame, using the mean method. When such methods are called on the entire DataFrame, a Series is returned. The indices of this Series are the column names in the DataFrame, and the values are the calculated values (in this case, mean values) for the DataFrame columns.

-
-
-
df.mean()
-
-
-
-
-
Nino12        23.209619
-Nino12anom     0.059725
-Nino3         25.936568
-Nino3anom      0.039428
-Nino4         28.625064
-Nino4anom      0.063814
-Nino34        27.076780
-Nino34anom     0.034894
-dtype: float64
-
-
-
-
-

If you want a specific statistic for only one column in the DataFrame, pull the column out of the DataFrame with dot notation, then call the statistic function (in this case, mean) on that column, as shown below:

-
-
-
df.Nino34.mean()
-
-
-
-
-
27.07677966101695
-
-
-
-
-
-
-

Subsetting Using the Datetime Column

-

Slicing is a useful technique for subsetting a DataFrame, but there are also other options that can be equally useful. In this section, some of these additional techniques are covered.

-

If your DataFrame uses datetime values for indices, you can select data from only one month using df.index.month. In this example, we specify the number 1, which only selects data from January.

-
-
-
# Uses the datetime column
-df[df.index.month == 1]
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anom
datetime
1982-01-0124.29-0.1725.870.2428.300.0026.720.15
1983-01-0127.422.9628.923.2929.000.7029.362.79
1984-01-0124.18-0.2824.82-0.8127.64-0.6625.64-0.93
1985-01-0123.59-0.8724.51-1.1227.71-0.5925.43-1.14
1986-01-0124.610.1524.73-0.9028.11-0.1925.79-0.78
1987-01-0125.300.8426.691.0629.020.7227.911.34
1988-01-0124.640.1826.120.4929.130.8327.320.75
1989-01-0124.09-0.3724.15-1.4826.54-1.7624.53-2.04
1990-01-0124.02-0.4425.34-0.2928.560.2626.55-0.02
1991-01-0123.86-0.6025.650.0229.000.7027.010.44
1992-01-0124.830.3727.001.3729.060.7628.411.84
1993-01-0124.43-0.0325.56-0.0728.600.3026.690.12
1994-01-0124.32-0.1425.710.0828.470.1726.600.03
1995-01-0125.330.8726.340.7129.200.9027.550.98
1996-01-0123.84-0.6224.96-0.6727.92-0.3825.74-0.83
1997-01-0123.67-0.7924.70-0.9328.410.1125.96-0.61
1998-01-0128.223.7628.943.3129.010.7129.102.53
1999-01-0123.73-0.7324.41-1.2226.59-1.7124.90-1.67
2000-01-0123.86-0.6023.88-1.7526.96-1.3424.65-1.92
2001-01-0123.88-0.5824.99-0.6427.50-0.8025.74-0.83
2002-01-0123.64-0.8225.09-0.5428.810.5126.50-0.07
2003-01-0124.38-0.0826.380.7529.250.9527.761.19
2004-01-0124.600.1425.920.2928.830.5326.740.17
2005-01-0124.470.0125.890.2629.210.9127.100.53
2006-01-0124.33-0.1325.00-0.6327.68-0.6225.64-0.93
2007-01-0124.990.5326.500.8728.930.6327.260.69
2008-01-0123.86-0.6024.13-1.5026.62-1.6824.71-1.86
2009-01-0124.42-0.1025.03-0.6027.42-0.8825.54-1.03
2010-01-0124.820.3026.631.0029.511.2128.071.50
2011-01-0124.08-0.4424.31-1.3226.72-1.5824.93-1.64
2012-01-0123.88-0.6424.90-0.7327.09-1.2125.49-1.08
2013-01-0124.00-0.5225.06-0.5728.28-0.0226.16-0.41
2014-01-0124.790.2725.26-0.3728.14-0.1726.06-0.51
2015-01-0124.13-0.3925.990.3629.160.8627.100.53
2016-01-0125.931.4128.212.5829.651.3529.172.60
2017-01-0125.751.2325.61-0.0228.18-0.1226.25-0.32
2018-01-0123.71-0.8124.48-1.1428.03-0.2725.82-0.75
2019-01-0125.100.5726.170.5529.000.6527.080.52
2020-01-0124.550.0225.810.2029.280.9327.090.53
2021-01-0123.89-0.6425.06-0.5527.10-1.2525.58-0.99
-
-
-

This example shows how to create a new column containing the month portion of the datetime index for each data row. The value returned by df.index.month is used to obtain the data for this new column:

-
-
-
df['month'] = df.index.month
-
-
-
-
-

This next example illustrates how to use the new month column to calculate average monthly values over the other data columns. First, we use the groupby method to group the other columns by the month. Second, we take the average (mean) to obtain the monthly averages. Finally, we plot the resulting data as a line plot by simply calling plot().

-
-
-
df.groupby('month').mean().plot();
-
-
-
-
-../../_images/c4601c390e490f5dbe7cfaa0cfe024da3db6e9f5d9703e5590c05916251f48e0.png -
-
-
-
-

Investigating Extreme Values

-

If you need to search for rows that meet a specific criterion, you can use conditional indexing. In this example, we search for rows where the Nino34 anomaly value (Nino34anom) is greater than 2:

-
-
-
df[df.Nino34anom > 2]
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anommonth
datetime
1982-11-0124.593.0027.622.6429.230.6028.812.1611
1982-12-0126.133.3428.393.2529.150.6629.212.6412
1983-01-0127.422.9628.923.2929.000.7029.362.791
1983-02-0128.092.0228.922.5528.790.6929.132.412
1997-08-0124.804.1527.842.8529.260.5828.842.028
1997-09-0124.404.0427.842.9929.320.6328.932.219
1997-10-0124.583.7628.173.2529.320.6629.232.5410
1997-11-0125.634.0428.553.5729.490.8629.322.6711
1997-12-0126.924.1328.763.6229.320.8329.262.6912
1998-01-0128.223.7628.943.3129.010.7129.102.531
1998-02-0128.982.9128.932.5628.870.7728.862.142
2015-08-0122.882.2427.332.3429.660.9828.892.078
2015-09-0122.912.5727.482.6329.731.0429.002.289
2015-10-0123.312.5227.582.6629.791.1229.152.4610
2015-11-0123.832.2427.912.9330.301.6729.602.9511
2015-12-0125.012.1927.992.8530.111.6329.392.8212
2016-01-0125.931.4128.212.5829.651.3529.172.601
2016-02-0126.810.6728.361.9929.551.4529.122.402
-
-
-

This example shows how to use the sort_values method on a DataFrame. This method sorts values in a DataFrame by the column specified as an argument.

-
-
-
df.sort_values('Nino34anom')
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anommonth
datetime
1988-11-0120.55-1.0423.03-1.9526.76-1.8724.27-2.3811
1988-12-0121.80-0.9923.07-2.0726.75-1.7424.33-2.2412
1988-10-0119.50-1.3223.17-1.7527.06-1.6024.62-2.0710
1989-01-0124.09-0.3724.15-1.4826.54-1.7624.53-2.041
2000-01-0123.86-0.6023.88-1.7526.96-1.3424.65-1.921
..............................
1997-11-0125.634.0428.553.5729.490.8629.322.6711
1997-12-0126.924.1328.763.6229.320.8329.262.6912
1983-01-0127.422.9628.923.2929.000.7029.362.791
2015-12-0125.012.1927.992.8530.111.6329.392.8212
2015-11-0123.832.2427.912.9330.301.6729.602.9511
-

472 rows × 9 columns

-
-
-

You can also reverse the ordering of the sort by specifying the ascending keyword argument as False:

-
-
-
df.sort_values('Nino34anom', ascending=False)
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anommonth
datetime
2015-11-0123.832.2427.912.9330.301.6729.602.9511
2015-12-0125.012.1927.992.8530.111.6329.392.8212
1983-01-0127.422.9628.923.2929.000.7029.362.791
1997-12-0126.924.1328.763.6229.320.8329.262.6912
1997-11-0125.634.0428.553.5729.490.8629.322.6711
..............................
2000-01-0123.86-0.6023.88-1.7526.96-1.3424.65-1.921
1989-01-0124.09-0.3724.15-1.4826.54-1.7624.53-2.041
1988-10-0119.50-1.3223.17-1.7527.06-1.6024.62-2.0710
1988-12-0121.80-0.9923.07-2.0726.75-1.7424.33-2.2412
1988-11-0120.55-1.0423.03-1.9526.76-1.8724.27-2.3811
-

472 rows × 9 columns

-
-
-
-
-

Resampling

-

In these examples, we illustrate a process known as resampling. Using resampling, you can change the frequency of index data values, reducing so-called ‘noise’ in a data plot. This is especially useful when working with timeseries data; plots can be equally effective with resampled data in these cases. The resampling performed in these examples converts monthly values to yearly averages. This is performed by passing the value ‘1Y’ to the resample method.

-
-
-
df.Nino34.plot();
-
-
-
-
-../../_images/ae4954a2e3dafba65da6ea4c9d0c47ef4dd166e276c3167ffd19b32a1b0f3b23.png -
-
-
-
-
df.Nino34.resample('1Y').mean().plot();
-
-
-
-
-../../_images/6ce189e2d9e1611badd2d85dbf550c29a26d309ae1572c1a9ec832183fc9c920.png -
-
-
-
-

Applying operations to a DataFrame

-

One of the most commonly used features in Pandas is the performing of calculations to multiple data values in a DataFrame simultaneously. Let’s first look at a familiar concept: a function that converts single values. The following example uses such a function to convert temperature values from degrees Celsius to Kelvin.

-
-
-
def convert_degc_to_kelvin(temperature_degc):
-    """
-    Converts from degrees celsius to Kelvin
-    """
-
-    return temperature_degc + 273.15
-
-
-
-
-
-
-
# Convert a single value
-convert_degc_to_kelvin(0)
-
-
-
-
-
273.15
-
-
-
-
-

The following examples instead illustrate a new concept: using such functions with DataFrames and Series. For the first example, we start by creating a Series; in order to do so, we subset the DataFrame by the Nino34 column. This has already been done earlier in this page; we do not need to create this Series again. We are using this particular Series for a reason: the data values are in degrees Celsius.

-
-
-
nino34_series
-
-
-
-
-
datetime
-1982-01-01    26.72
-1982-02-01    26.70
-1982-03-01    27.20
-1982-04-01    28.02
-1982-05-01    28.54
-              ...  
-2020-12-01    25.53
-2021-01-01    25.58
-2021-02-01    25.81
-2021-03-01    26.75
-2021-04-01    27.40
-Name: Nino34, Length: 472, dtype: float64
-
-
-
-
-

Here, we look at a portion of an existing DataFrame column. Notice that this column portion is a Pandas Series.

-
-
-
type(df.Nino12[0:10])
-
-
-
-
-
pandas.core.series.Series
-
-
-
-
-

As shown in the following example, each Pandas Series contains a representation of its data in numpy format. Therefore, it is possible to convert a Pandas Series into a numpy array; this is done using the .values method:

-
-
-
type(df.Nino12.values[0:10])
-
-
-
-
-
numpy.ndarray
-
-
-
-
-

This example illustrates how to use the temperature-conversion function defined above on a Series object. Just as calling the function with a single value returns a single value, calling the function on a Series object returns another Series object. The function performs the temperature conversion on each data value in the Series, and returns a Series with all values converted.

-
-
-
convert_degc_to_kelvin(nino34_series)
-
-
-
-
-
datetime
-1982-01-01    299.87
-1982-02-01    299.85
-1982-03-01    300.35
-1982-04-01    301.17
-1982-05-01    301.69
-               ...  
-2020-12-01    298.68
-2021-01-01    298.73
-2021-02-01    298.96
-2021-03-01    299.90
-2021-04-01    300.55
-Name: Nino34, Length: 472, dtype: float64
-
-
-
-
-

If we call the .values method on the Series passed to the function, the Series is converted to a numpy array, as described above. The function then converts each value in the numpy array, and returns a new numpy array with all values sorted.

-
-

Warning

-

It is recommended to only convert Series to NumPy arrays when necessary; doing so removes the label information that enables much of the Pandas core functionality.

-
-
-
-
convert_degc_to_kelvin(nino34_series.values)
-
-
-
-
-
array([299.87, 299.85, 300.35, 301.17, 301.69, 301.9 , 301.25, 301.08,
-       301.26, 301.79, 301.96, 302.36, 302.51, 302.28, 302.18, 302.06,
-       302.04, 301.39, 300.22, 299.68, 299.59, 299.02, 298.73, 298.74,
-       298.79, 299.54, 300.01, 300.54, 300.54, 300.01, 299.89, 299.49,
-       299.58, 299.08, 298.56, 298.15, 298.58, 298.82, 299.38, 299.95,
-       300.26, 300.01, 299.84, 299.65, 299.4 , 299.34, 299.34, 299.26,
-       298.94, 299.09, 299.8 , 300.59, 300.65, 300.84, 300.52, 300.3 ,
-       300.48, 300.72, 300.88, 300.85, 301.06, 301.17, 301.62, 301.95,
-       301.9 , 302.18, 301.95, 301.73, 301.54, 301.22, 301.14, 300.75,
-       300.47, 300.37, 300.46, 300.47, 299.63, 299.26, 298.72, 298.39,
-       298.58, 297.77, 297.42, 297.48, 297.68, 298.48, 299.05, 299.84,
-       300.24, 300.13, 299.89, 299.48, 299.4 , 299.41, 299.39, 299.53,
-       299.7 , 300.1 , 300.61, 301.17, 301.21, 300.73, 300.4 , 300.2 ,
-       299.9 , 300.13, 299.87, 300.06, 300.16, 300.08, 300.4 , 301.13,
-       301.5 , 301.51, 301.07, 300.59, 300.22, 300.78, 301.01, 301.52,
-       301.56, 301.78, 301.98, 302.29, 302.14, 301.17, 300.68, 299.79,
-       299.63, 299.49, 299.66, 299.88, 299.84, 300.12, 300.81, 301.74,
-       301.97, 301.43, 300.7 , 299.99, 300.07, 300.08, 300.06, 299.91,
-       299.75, 299.74, 300.42, 301.05, 301.19, 301.14, 300.5 , 300.5 ,
-       300.15, 300.64, 301.02, 301.02, 300.7 , 300.6 , 300.78, 301.08,
-       300.88, 300.74, 300.16, 299.48, 299.11, 298.82, 298.81, 298.72,
-       298.89, 299.  , 299.77, 300.51, 300.52, 300.47, 300.24, 299.71,
-       299.5 , 299.39, 299.34, 299.17, 299.11, 299.51, 300.18, 301.18,
-       301.75, 302.09, 302.07, 301.99, 302.08, 302.38, 302.47, 302.41,
-       302.25, 302.01, 301.82, 301.71, 301.62, 299.87, 299.09, 298.64,
-       298.76, 298.49, 298.33, 297.94, 298.05, 298.56, 299.4 , 299.99,
-       300.12, 299.75, 299.5 , 298.74, 298.86, 298.79, 298.27, 298.05,
-       297.8 , 298.34, 299.23, 300.16, 300.27, 300.18, 299.87, 299.6 ,
-       299.36, 299.11, 298.93, 298.74, 298.89, 299.26, 299.99, 300.67,
-       300.75, 300.83, 300.47, 300.02, 299.7 , 299.74, 299.6 , 299.32,
-       299.65, 300.1 , 300.47, 301.09, 301.3 , 301.58, 301.13, 300.94,
-       300.98, 301.2 , 301.42, 301.24, 300.91, 300.64, 300.96, 300.96,
-       300.52, 300.63, 300.58, 300.  , 300.11, 300.34, 300.2 , 300.04,
-       299.89, 300.01, 300.25, 300.99, 301.21, 300.91, 300.84, 300.69,
-       300.62, 300.53, 300.46, 300.46, 300.25, 300.11, 300.7 , 301.22,
-       301.35, 301.2 , 300.62, 300.03, 299.78, 299.9 , 299.49, 299.04,
-       298.79, 299.23, 299.72, 300.74, 301.06, 301.  , 300.5 , 300.37,
-       300.49, 300.62, 300.88, 300.91, 300.41, 299.96, 300.33, 300.93,
-       300.72, 300.7 , 299.94, 299.35, 298.92, 298.37, 298.21, 298.12,
-       297.86, 297.98, 299.22, 299.98, 300.33, 300.32, 300.34, 300.  ,
-       299.59, 299.48, 299.45, 298.89, 298.69, 299.19, 299.82, 300.65,
-       301.18, 301.26, 301.09, 300.68, 300.62, 300.78, 301.34, 301.45,
-       301.22, 301.09, 301.44, 301.51, 300.83, 300.15, 299.24, 298.65,
-       298.22, 298.16, 298.22, 298.1 , 298.08, 298.61, 299.38, 300.17,
-       300.57, 300.61, 300.11, 299.34, 299.13, 298.87, 298.75, 298.68,
-       298.64, 299.18, 299.78, 300.53, 300.95, 301.1 , 300.9 , 300.7 ,
-       300.39, 300.13, 300.16, 299.61, 299.31, 299.47, 300.15, 300.83,
-       300.72, 300.58, 300.06, 299.69, 299.8 , 299.51, 299.8 , 299.68,
-       299.21, 299.33, 300.14, 301.16, 301.46, 301.26, 300.55, 300.17,
-       300.32, 300.32, 300.65, 300.5 , 300.25, 300.44, 300.94, 301.71,
-       302.03, 302.11, 301.97, 302.04, 302.15, 302.3 , 302.75, 302.54,
-       302.32, 302.27, 302.05, 302.02, 301.3 , 300.68, 299.88, 299.43,
-       299.26, 299.11, 299.25, 299.31, 299.4 , 300.02, 300.49, 301.25,
-       301.45, 301.34, 300.76, 299.82, 299.44, 299.38, 298.94, 298.95,
-       298.97, 298.98, 299.63, 300.57, 300.87, 301.  , 300.67, 300.26,
-       300.25, 300.7 , 300.79, 300.68, 300.23, 300.56, 301.37, 301.75,
-       301.72, 301.39, 300.78, 300.12, 299.85, 300.46, 300.41, 300.22,
-       300.24, 300.29, 300.97, 301.47, 300.74, 300.45, 300.04, 299.33,
-       298.92, 298.45, 298.49, 298.68, 298.73, 298.96, 299.9 , 300.55])
-
-
-
-
-

As described above, when our temperature-conversion function accepts a Series as an argument, it returns a Series. We can directly assign this returned Series to a new column in our DataFrame, as shown below:

-
-
-
df['Nino34_degK'] = convert_degc_to_kelvin(nino34_series)
-
-
-
-
-
-
-
df.Nino34_degK
-
-
-
-
-
datetime
-1982-01-01    299.87
-1982-02-01    299.85
-1982-03-01    300.35
-1982-04-01    301.17
-1982-05-01    301.69
-               ...  
-2020-12-01    298.68
-2021-01-01    298.73
-2021-02-01    298.96
-2021-03-01    299.90
-2021-04-01    300.55
-Name: Nino34_degK, Length: 472, dtype: float64
-
-
-
-
-

In this final example, we demonstrate the use of the to_csv method to save a DataFrame as a .csv file. This example also demonstrates the read_csv method, which reads .csv files into Pandas DataFrames.

-
-
-
df.to_csv('nino_analyzed_output.csv')
-
-
-
-
-
-
-
pd.read_csv('nino_analyzed_output.csv', index_col=0, parse_dates=True)
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nino12Nino12anomNino3Nino3anomNino4Nino4anomNino34Nino34anommonthNino34_degK
datetime
1982-01-0124.29-0.1725.870.2428.300.0026.720.151299.87
1982-02-0125.49-0.5826.380.0128.210.1126.70-0.022299.85
1982-03-0125.21-1.3126.98-0.1628.410.2227.20-0.023300.35
1982-04-0124.50-0.9727.680.1828.920.4228.020.244301.17
1982-05-0123.97-0.2327.790.7129.490.7028.540.695301.69
.................................
2020-12-0122.16-0.6024.38-0.8327.65-0.9525.53-1.1212298.68
2021-01-0123.89-0.6425.06-0.5527.10-1.2525.58-0.991298.73
2021-02-0125.55-0.6625.80-0.5727.20-1.0025.81-0.922298.96
2021-03-0126.48-0.2626.80-0.3927.79-0.5526.75-0.513299.90
2021-04-0124.89-0.8026.96-0.6528.47-0.2127.40-0.494300.55
-

472 rows × 10 columns

-
-
-
-
-
-
-

Summary

-
    -
  • Pandas is a very powerful tool for working with tabular (i.e., spreadsheet-style) data

  • -
  • There are multiple ways of subsetting your pandas dataframe or series

  • -
  • Pandas allows you to refer to subsets of data by label, which generally makes code more readable and more robust

  • -
  • Pandas can be helpful for exploratory data analysis, including plotting and basic statistics

  • -
  • One can apply calculations to pandas dataframes and save the output via csv files

  • -
-
-

What’s Next?

-

In the next notebook, we will look more into using pandas for more in-depth data analysis.

-
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/xarray.html b/_preview/419/core/xarray.html deleted file mode 100644 index 49ec72c8d..000000000 --- a/_preview/419/core/xarray.html +++ /dev/null @@ -1,859 +0,0 @@ - - - - - - - - Xarray — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -

xarray Logo

-
-

Xarray

-

This section contains tutorials on using Xarray. Xarray is used widely in the geosciences and beyond for analysis of gridded N-dimensional datasets.

-
-

From the Xarray website:

-
-

Xarray (formerly Xray) is an open source project and Python package that makes working with labelled multi-dimensional arrays simple, efficient, and fun!

-

Xarray introduces labels in the form of dimensions, coordinates and attributes on top of raw NumPy-like arrays, which allows for a more intuitive, more concise, and less error-prone developer experience. The package includes a large and growing library of domain-agnostic functions for advanced analytics and visualization with these data structures.

-

Xarray is inspired by and borrows heavily from pandas, the popular data analysis package focused on labelled tabular data. It is particularly tailored to working with netCDF files, which were the source of xarray’s data model, and integrates tightly with dask for parallel computing.

-
-

You should have a basic familiarity with Numpy arrays prior to working through the Xarray notebooks presented here.

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/xarray/computation-masking.html b/_preview/419/core/xarray/computation-masking.html deleted file mode 100644 index 6088d539c..000000000 --- a/_preview/419/core/xarray/computation-masking.html +++ /dev/null @@ -1,9482 +0,0 @@ - - - - - - - - Computations and Masks with Xarray — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -
-

Computations and Masks with Xarray

-
-
-

Overview

-

In this tutorial, we will cover the following topics:

-
    -
  1. Performing basic arithmetic on DataArrays and Datasets

  2. -
  3. Performing aggregation (i.e., reduction) along single or multiple dimensions of a DataArray or Dataset

  4. -
  5. Computing climatologies and anomalies of data using Xarray’s “split-apply-combine” approach, via the .groupby() method

  6. -
  7. Performing weighted-reduction operations along single or multiple dimensions of a DataArray or Dataset

  8. -
  9. Providing a broad overview of Xarray’s data-masking capability

  10. -
  11. Using the .where() method to mask Xarray data

  12. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Introduction to Xarray

Necessary

-
    -
  • Time to learn: 60 minutes

  • -
-
-
-
-

Imports

-

In order to work with data and plotting, we must import NumPy, Matplotlib, and Xarray. These packages are covered in greater detail in earlier tutorials. We also import a package that allows quick download of Pythia example datasets.

-
-
-
import matplotlib.pyplot as plt
-import numpy as np
-import xarray as xr
-from pythia_datasets import DATASETS
-
-
-
-
-
-
-

Data Setup

-

The bulk of the examples in this tutorial make use of a single dataset. This dataset contains monthly sea surface temperature (SST, call ‘tos’ here) data, and is obtained from the Community Earth System Model v2 (CESM2). (For this tutorial, however, the dataset will be retrieved from the Pythia example data repository.) The following example illustrates the process of retrieving this Global Climate Model dataset:

-
-
-
filepath = DATASETS.fetch('CESM2_sst_data.nc')
-ds = xr.open_dataset(filepath)
-ds
-
-
-
-
-
Downloading file 'CESM2_sst_data.nc' from 'https://github.com/ProjectPythia/pythia-datasets/raw/main/data/CESM2_sst_data.nc' to '/home/runner/.cache/pythia-datasets'.
-
-
-
/usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/xarray/conventions.py:431: SerializationWarning: variable 'tos' has multiple fill values {1e+20, 1e+20}, decoding all values to NaN.
-  new_vars[k] = decode_cf_variable(
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:    (time: 180, d2: 2, lat: 180, lon: 360)
-Coordinates:
-  * time       (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat        (lat) float64 -89.5 -88.5 -87.5 -86.5 ... 86.5 87.5 88.5 89.5
-  * lon        (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 356.5 357.5 358.5 359.5
-Dimensions without coordinates: d2
-Data variables:
-    time_bnds  (time, d2) object ...
-    lat_bnds   (lat, d2) float64 ...
-    lon_bnds   (lon, d2) float64 ...
-    tos        (time, lat, lon) float32 ...
-Attributes: (12/45)
-    Conventions:            CF-1.7 CMIP-6.2
-    activity_id:            CMIP
-    branch_method:          standard
-    branch_time_in_child:   674885.0
-    branch_time_in_parent:  219000.0
-    case_id:                972
-    ...                     ...
-    sub_experiment_id:      none
-    table_id:               Omon
-    tracking_id:            hdl:21.14100/2975ffd3-1d7b-47e3-961a-33f212ea4eb2
-    variable_id:            tos
-    variant_info:           CMIP6 20th century experiments (1850-2014) with C...
-    variant_label:          r11i1p1f1
-
-
-
-

Arithmetic Operations

-

In a similar fashion to NumPy arrays, performing an arithmetic operation on a DataArray will automatically perform the operation on all array values; this is known as vectorization. To illustrate the process of vectorization, the following example converts the air temperature data from units of degrees Celsius to units of Kelvin:

-
-
-
ds.tos + 273.15
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180, lat: 180, lon: 360)>
-array([[[      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        ...,
-        [271.3552 , 271.3553 , 271.3554 , ..., 271.35495, 271.355  ,
-         271.3551 ],
-        [271.36005, 271.36014, 271.36023, ..., 271.35986, 271.35992,
-         271.36   ],
-        [271.36447, 271.36453, 271.3646 , ..., 271.3643 , 271.36435,
-         271.3644 ]],
-
-       [[      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-...
-        [271.40677, 271.40674, 271.4067 , ..., 271.40695, 271.4069 ,
-         271.40683],
-        [271.41296, 271.41293, 271.41293, ..., 271.41306, 271.413  ,
-         271.41296],
-        [271.41772, 271.41772, 271.41772, ..., 271.41766, 271.4177 ,
-         271.4177 ]],
-
-       [[      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        ...,
-        [271.39386, 271.39383, 271.3938 , ..., 271.39407, 271.394  ,
-         271.39392],
-        [271.39935, 271.39932, 271.39932, ..., 271.39948, 271.39944,
-         271.39938],
-        [271.40372, 271.40372, 271.40375, ..., 271.4037 , 271.4037 ,
-         271.40372]]], dtype=float32)
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-
-

In addition, there are many other arithmetic operations that can be performed on DataArrays. In this example, we demonstrate squaring the original Celsius values of our air temperature data:

-
-
-
ds.tos**2
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180, lat: 180, lon: 360)>
-array([[[      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        ...,
-        [3.2213385, 3.2209656, 3.220537 , ..., 3.2221622, 3.221913 ,
-         3.2216525],
-        [3.203904 , 3.203617 , 3.2032912, ..., 3.2045207, 3.2043478,
-         3.2041442],
-        [3.1881146, 3.1879027, 3.1876712, ..., 3.188714 , 3.1885312,
-         3.1883302]],
-
-       [[      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-...
-        [3.0388296, 3.0389647, 3.0390673, ..., 3.038165 , 3.0383828,
-         3.0386322],
-        [3.0173173, 3.0173445, 3.0173297, ..., 3.0169601, 3.0171173,
-         3.0172386],
-        [3.000791 , 3.0007784, 3.0007539, ..., 3.000933 , 3.000896 ,
-         3.0008452]],
-
-       [[      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        [      nan,       nan,       nan, ...,       nan,       nan,
-               nan],
-        ...,
-        [3.0839543, 3.0841148, 3.0842566, ..., 3.0832636, 3.0834875,
-         3.0837412],
-        [3.064733 , 3.0648024, 3.0648358, ..., 3.0642793, 3.0644639,
-         3.0646174],
-        [3.0494578, 3.0494475, 3.0494263, ..., 3.049596 , 3.0495603,
-         3.0495107]]], dtype=float32)
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-
-
-
-

Aggregation Methods

-

A common practice in the field of data analysis is aggregation. Aggregation is the process of reducing data through methods such as sum(), mean(), median(), min(), and max(), in order to gain greater insight into the nature of large datasets. In this set of examples, we demonstrate correct usage of a select group of aggregation methods:

-

Compute the mean:

-
-
-
ds.tos.mean()
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' ()>
-array(14.250171, dtype=float32)
-
-

Notice that we did not specify the dim keyword argument; this means that the function was applied over all of the dataset’s dimensions. In other words, the aggregation method computed the mean of every element of the temperature dataset across every temporal and spatial data point. However, if a dimension name is used with the dim keyword argument, the aggregation method computes an aggregation along the given dimension. In this next example, we use aggregation to calculate the temporal mean across all spatial data; this is performed by providing the dimension name 'time' to the dim keyword argument:

-
-
-
ds.tos.mean(dim='time').plot(size=7);
-
-
-
-
-../../_images/5db4c4eb91eb2b15d051d432d790b744da1bc537940f29c09216d60aadbe3018.png -
-
-

There are many other combinations of aggregation methods and dimensions on which to perform these methods. In this example, we compute the temporal minimum:

-
-
-
ds.tos.min(dim=['time'])
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (lat: 180, lon: 360)>
-array([[       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       [       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       [       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       ...,
-       [-1.8083605, -1.8083031, -1.8082187, ..., -1.8083988, -1.8083944,
-        -1.8083915],
-       [-1.8025414, -1.8024837, -1.8024155, ..., -1.8026428, -1.8026177,
-        -1.8025846],
-       [-1.7984415, -1.7983989, -1.7983514, ..., -1.7985678, -1.7985296,
-        -1.7984871]], dtype=float32)
-Coordinates:
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-
-

This example computes the spatial sum. Note that this dataset contains no altitude data; as such, the required spatial dimensions passed to the method consist only of latitude and longitude.

-
-
-
ds.tos.sum(dim=['lat', 'lon'])
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180)>
-array([603767.  , 607702.5 , 603976.5 , 599373.56, 595119.94, 595716.75,
-       598177.3 , 600670.6 , 597825.56, 591869.  , 590507.7 , 597189.2 ,
-       605954.06, 609151.  , 606868.9 , 602329.9 , 599465.75, 601205.5 ,
-       605144.4 , 608588.5 , 604046.9 , 598927.75, 597519.75, 603876.9 ,
-       612424.44, 615765.2 , 612615.44, 606310.6 , 602034.4 , 600784.9 ,
-       602013.5 , 603142.2 , 598850.9 , 591917.44, 589234.56, 596162.5 ,
-       602942.06, 607196.9 , 604928.2 , 601735.6 , 599011.8 , 599490.9 ,
-       600801.44, 602786.94, 598867.2 , 594081.8 , 593736.25, 598995.6 ,
-       607285.25, 611901.06, 609562.75, 603527.3 , 600215.4 , 601372.6 ,
-       604144.5 , 605376.75, 601256.2 , 595245.2 , 594002.06, 600490.4 ,
-       611878.6 , 616563.  , 613050.8 , 605734.  , 600808.75, 600898.06,
-       603930.56, 605644.7 , 599917.5 , 592048.06, 590082.8 , 596950.7 ,
-       607701.94, 610844.7 , 609509.6 , 603380.94, 599838.1 , 600334.25,
-       604386.6 , 607848.1 , 602155.2 , 594949.06, 593815.06, 598365.3 ,
-       608730.8 , 612056.5 , 609922.5 , 603077.1 , 600134.1 , 602821.2 ,
-       606152.75, 610257.8 , 604685.8 , 596858.  , 592894.8 , 599944.9 ,
-       609764.44, 614610.75, 611434.75, 605606.4 , 603790.94, 605750.2 ,
-       609250.06, 612935.7 , 609645.06, 601706.4 , 598896.5 , 605349.75,
-       614671.8 , 618686.7 , 615895.2 , 609438.2 , 605399.56, 606126.75,
-       607942.3 , 609680.4 , 604814.25, 595841.94, 591908.44, 595638.7 ,
-       604798.94, 611327.1 , 609765.7 , 603727.56, 600970.  , 602514.  ,
-       606303.7 , 609225.25, 603724.3 , 595944.8 , 594477.4 , 597807.4 ,
-       607379.06, 611808.56, 610112.94, 607196.3 , 604733.06, 605488.25,
-       610048.3 , 612655.75, 608906.25, 602349.7 , 601754.2 , 609220.4 ,
-       619367.1 , 623783.2 , 619949.7 , 613369.06, 610190.8 , 611091.2 ,
-       614213.44, 615665.06, 611722.2 , 606259.56, 605970.2 , 611463.3 ,
-       619794.6 , 626036.5 , 623085.44, 616295.9 , 611886.3 , 611881.9 ,
-       614420.75, 616853.56, 610375.44, 603471.5 , 602108.25, 608094.3 ,
-       617450.7 , 623508.7 , 619830.2 , 612033.3 , 608737.2 , 610105.25,
-       613692.7 , 616360.44, 611735.4 , 606512.7 , 604249.44, 608777.44],
-      dtype=float32)
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-
-

For the last example in this set of aggregation examples, we compute the temporal median:

-
-
-
ds.tos.median(dim='time')
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (lat: 180, lon: 360)>
-array([[       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       [       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       [       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       ...,
-       [-1.7648907, -1.7648032, -1.7647004, ..., -1.7650614, -1.7650102,
-        -1.7649589],
-       [-1.7590305, -1.7589546, -1.7588665, ..., -1.7591925, -1.7591486,
-        -1.759095 ],
-       [-1.7536805, -1.753602 , -1.7535168, ..., -1.753901 , -1.753833 ,
-        -1.7537591]], dtype=float32)
-Coordinates:
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-
-

In addition, there are many other commonly used aggregation methods in Xarray. Some of the more popular aggregation methods are summarized in the following table:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Aggregation

Description

count()

Total number of items

mean(), median()

Mean and median

min(), max()

Minimum and maximum

std(), var()

Standard deviation and variance

prod()

Compute product of elements

sum()

Compute sum of elements

argmin(), argmax()

Find index of minimum and maximum value

-
-
-

GroupBy: Split, Apply, Combine

-

While we can obtain useful summaries of datasets using simple aggregation methods, it is more often the case that aggregation must be performed over coordinate labels or groups. In order to perform this type of aggregation, it is helpful to use the split-apply-combine workflow. Fortunately, Xarray provides this functionality for DataArrays and Datasets by means of the groupby operation. The following figure illustrates the split-apply-combine workflow in detail:

-../../_images/xarray-split-apply-combine.jpeg -

Based on the above figure, you can understand the split-apply-combine process performed by groupby. In detail, the steps of this process are:

-
    -
  • The split step involves breaking up and grouping an xarray Dataset or DataArray depending on the value of the specified group key.

  • -
  • The apply step involves computing some function, usually an aggregate, transformation, or filtering, within the individual groups.

  • -
  • The combine step merges the results of these operations into an output xarray Dataset or DataArray.

  • -
-

In this set of examples, we will remove the seasonal cycle (also known as a climatology) from our dataset using groupby. There are many types of input that can be provided to groupby; a full list can be found in Xarray’s groupby user guide.

-

In this first example, we plot data to illustrate the annual cycle described above. We first select the grid point closest to a specific latitude-longitude point. Once we have this grid point, we can plot a temporal series of sea-surface temperature (SST) data at that location. Reviewing the generated plot, the annual cycle of the data becomes clear.

-
-
-
ds.tos.sel(lon=310, lat=50, method='nearest').plot();
-
-
-
-
-../../_images/cadbe3c8997ffc29b85b27fa7ff43559e9833077e27116a2d630d39bdf4d65f5.png -
-
-
-

Split

-

The first step of the split-apply-combine process is splitting. As described above, this step involves splitting a dataset into groups, with each group matching a group key. In this example, we split the SST data using months as a group key. Therefore, there is one resulting group for January data, one for February data, etc. This code illustrates how to perform such a split:

-
-
-
ds.tos.groupby(ds.time.dt.month)
-
-
-
-
-
DataArrayGroupBy, grouped over 'month'
-12 groups with labels 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12.
-
-
-
-
-

In the above code example, we are extracting components of date/time data by way of the time coordinate’s .dt attribute. This attribute is a DatetimeAccessor object that contains additional attributes for units of time, such as hour, day, and year. Since we are splitting the data into monthly data, we use the month attribute of .dt in this example. (In addition, there exists similar functionality in Pandas; see the official documentation for details.)

-

In addition, there is a more concise syntax that can be used in specific instances. This syntax can be used if the variable on which the grouping is performed is already present in the dataset. The following example illustrates this syntax; it is functionally equivalent to the syntax used in the above example.

-
-
-
ds.tos.groupby('time.month')
-
-
-
-
-
DataArrayGroupBy, grouped over 'month'
-12 groups with labels 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12.
-
-
-
-
-
-
-

Apply & Combine

-

Now that we have split our data into groups, the next step is to apply a calculation to the groups. There are two types of calculation that can be applied:

-
    -
  • aggregation: reduces the size of the group

  • -
  • transformation: preserves the group’s full size

  • -
-

After a calculation is applied to the groups, Xarray will automatically combine the groups back into a single object, completing the split-apply-combine workflow.

-
-

Compute climatology

-

In this example, we use the split-apply-combine workflow to calculate the monthly climatology at every point in the dataset. Notice that we are using the month DatetimeAccessor, as described above, as well as the .mean() aggregation function:

-
-
-
tos_clim = ds.tos.groupby('time.month').mean()
-tos_clim
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (month: 12, lat: 180, lon: 360)>
-array([[[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        ...,
-        [-1.780786 , -1.780688 , -1.7805718, ..., -1.7809757,
-         -1.7809197, -1.7808627],
-        [-1.7745041, -1.7744204, -1.7743237, ..., -1.77467  ,
-         -1.774626 , -1.7745715],
-        [-1.7691481, -1.7690798, -1.7690051, ..., -1.7693441,
-         -1.7692844, -1.7692182]],
-
-       [[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-...
-        [-1.7605033, -1.760397 , -1.7602725, ..., -1.760718 ,
-         -1.7606541, -1.7605885],
-        [-1.7544289, -1.7543424, -1.7542422, ..., -1.754608 ,
-         -1.754559 , -1.7545002],
-        [-1.7492163, -1.749148 , -1.7490736, ..., -1.7494118,
-         -1.7493519, -1.7492864]],
-
-       [[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        ...,
-        [-1.7711828, -1.7710832, -1.7709653, ..., -1.7713748,
-         -1.7713183, -1.7712607],
-        [-1.7648666, -1.7647841, -1.7646879, ..., -1.7650299,
-         -1.7649865, -1.7649331],
-        [-1.759478 , -1.7594113, -1.7593384, ..., -1.7596704,
-         -1.7596117, -1.759547 ]]], dtype=float32)
-Coordinates:
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-  * month    (month) int64 1 2 3 4 5 6 7 8 9 10 11 12
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-

Now that we have a DataArray containing the climatology data, we can plot the data in different ways. In this example, we plot the climatology at a specific latitude-longitude point:

-
-
-
tos_clim.sel(lon=310, lat=50, method='nearest').plot();
-
-
-
-
-../../_images/1ff05bdb2e3b4f454f8f3c3ed26ba9cc262eab7ea28e8c1654de8b5cbc54d4d2.png -
-
-

In this example, we plot the zonal mean climatology:

-
-
-
tos_clim.mean(dim='lon').transpose().plot.contourf(levels=12, cmap='turbo');
-
-
-
-
-../../_images/0bab2f849a072a2d2afb9b00c213e1d076a475795bf2691d1279154d2791e207.png -
-
-

Finally, this example calculates and plots the difference between the climatology for January and the climatology for December:

-
-
-
(tos_clim.sel(month=1) - tos_clim.sel(month=12)).plot(size=6, robust=True);
-
-
-
-
-../../_images/7e827ab6b1fe7eb8e999229e0d75ccaae3597c92ae7fbc243178901477e8ea9c.png -
-
-
-
-

Compute anomaly

-

In this example, we compute the anomaly of the original data by removing the climatology from the data values. As shown in previous examples, the climatology is first calculated. The calculated climatology is then removed from the data using arithmetic and Xarray’s groupby method:

-
-
-
gb = ds.tos.groupby('time.month')
-tos_anom = gb - gb.mean(dim='time')
-tos_anom
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180, lat: 180, lon: 360)>
-array([[[        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        [        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        [        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        ...,
-        [-0.01402271, -0.01401687, -0.01401365, ..., -0.01406252,
-         -0.01404917, -0.01403356],
-        [-0.01544118, -0.01544476, -0.01545036, ..., -0.0154475 ,
-         -0.01544321, -0.01544082],
-        [-0.01638114, -0.01639009, -0.01639998, ..., -0.01635301,
-         -0.01636147, -0.01637137]],
-
-       [[        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        [        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        [        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-...
-        [ 0.01727939,  0.01713431,  0.01698041, ...,  0.0176847 ,
-          0.01755834,  0.01742125],
-        [ 0.0173862 ,  0.0172919 ,  0.01719594, ...,  0.01766813,
-          0.01757395,  0.01748013],
-        [ 0.01693714,  0.01687253,  0.01680517, ...,  0.01709175,
-          0.0170424 ,  0.01699162]],
-
-       [[        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        [        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        [        nan,         nan,         nan, ...,         nan,
-                 nan,         nan],
-        ...,
-        [ 0.01506364,  0.01491845,  0.01476014, ...,  0.01545238,
-          0.0153321 ,  0.01520228],
-        [ 0.0142287 ,  0.01412642,  0.01402068, ...,  0.0145216 ,
-          0.01442552,  0.01432824],
-        [ 0.01320827,  0.01314461,  0.01307774, ...,  0.0133611 ,
-          0.0133127 ,  0.01326215]]], dtype=float32)
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-    month    (time) int64 1 2 3 4 5 6 7 8 9 10 11 ... 2 3 4 5 6 7 8 9 10 11 12
-
-
-
-
tos_anom.sel(lon=310, lat=50, method='nearest').plot();
-
-
-
-
-../../_images/75e950aecc9ff41c0670cd2c0ea2299592e350151976a680f3650234d9e01826.png -
-
-

In this example, we compute and plot our dataset’s mean global anomaly over time. In order to specify global data, we must provide both lat and lon to the mean() method’s dim keyword argument:

-
-
-
unweighted_mean_global_anom = tos_anom.mean(dim=['lat', 'lon'])
-unweighted_mean_global_anom.plot();
-
-
-
-
-../../_images/18cef30c144b43ab6ee933d735fa63322c833299f5f94b3111aefe0109bd3278.png -
-
-

Many geoscientific algorithms perform operations over data contained in many different grid cells. However, if the grid cells are not equivalent in size, the operation is not scientifically valid by default. Fortunately, this can be fixed by weighting the data in each grid cell by the size of the cell. Weighting data in Xarray is simple, as Xarray has a built-in weighting method, known as .weighted().

-
-

In this example, we again make use of the Pythia example data library to load a new CESM2 dataset. Contained in this dataset are weights corresponding to the grid cells in our anomaly data:

-
-
-
filepath2 = DATASETS.fetch('CESM2_grid_variables.nc')
-areacello = xr.open_dataset(filepath2).areacello
-areacello
-
-
-
-
-
Downloading file 'CESM2_grid_variables.nc' from 'https://github.com/ProjectPythia/pythia-datasets/raw/main/data/CESM2_grid_variables.nc' to '/home/runner/.cache/pythia-datasets'.
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'areacello' (lat: 180, lon: 360)>
-[64800 values with dtype=float64]
-Coordinates:
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-Attributes: (12/17)
-    cell_methods:   area: sum
-    comment:        TAREA
-    description:    Cell areas for any grid used to report ocean variables an...
-    frequency:      fx
-    id:             areacello
-    long_name:      Grid-Cell Area for Ocean Variables
-    ...             ...
-    time_label:     None
-    time_title:     No temporal dimensions ... fixed field
-    title:          Grid-Cell Area for Ocean Variables
-    type:           real
-    units:          m2
-    variable_id:    areacello
-
-

In a similar fashion to a previous example, this example calculates mean global anomaly. However, this example makes use of the .weighted() method and the newly loaded CESM2 dataset to weight the grid cell data as described above:

-
-
-
weighted_mean_global_anom = tos_anom.weighted(areacello).mean(dim=['lat', 'lon'])
-
-
-
-
-

This example plots both unweighted and weighted mean data, which illustrates the degree of scientific error with unweighted data:

-
-
-
unweighted_mean_global_anom.plot(size=7)
-weighted_mean_global_anom.plot()
-plt.legend(['unweighted', 'weighted']);
-
-
-
-
-../../_images/7a5aedf104c69c787c53494e8abe107fdeb84f088620539e6506ea35c494be42.png -
-
-
-
-
-
-

Other high level computation functionality

- -

This example illustrates the resampling of a dataset’s time dimension to annual frequency:

-
-
-
r = ds.tos.resample(time='AS')
-r
-
-
-
-
-
DataArrayResample, grouped over '__resample_dim__'
-15 groups with labels 2000-01-01, 00:00:00, ..., 201....
-
-
-
-
-
-
-
r.mean()
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 15, lat: 180, lon: 360)>
-array([[[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        ...,
-        [-1.7474419, -1.7474264, -1.7474008, ..., -1.7474308,
-         -1.7474365, -1.7474445],
-        [-1.7424874, -1.7424612, -1.7424251, ..., -1.742536 ,
-         -1.7425283, -1.7425116],
-        [-1.7382039, -1.7381679, -1.7381277, ..., -1.7383199,
-         -1.7382846, -1.7382454]],
-
-       [[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-...
-        [-1.6902231, -1.6899008, -1.6895409, ..., -1.6910189,
-         -1.6907759, -1.6905178],
-        [-1.6879102, -1.6876906, -1.6874666, ..., -1.6885366,
-         -1.6883289, -1.688121 ],
-        [-1.6883243, -1.6881752, -1.6880217, ..., -1.6886654,
-         -1.6885542, -1.6884427]],
-
-       [[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        ...,
-        [-1.6893266, -1.6893964, -1.6894479, ..., -1.6889572,
-         -1.6890831, -1.6892204],
-        [-1.6776317, -1.6777302, -1.6778082, ..., -1.6771463,
-         -1.6773272, -1.677492 ],
-        [-1.672563 , -1.6726688, -1.6727766, ..., -1.6723493,
-         -1.6724195, -1.6724887]]], dtype=float32)
-Coordinates:
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-  * time     (time) object 2000-01-01 00:00:00 ... 2014-01-01 00:00:00
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-

This example illustrates using the rolling method to compute averages in a moving window of 5 months of data:

-
-
-
m_avg = ds.tos.rolling(time=5, center=True).mean()
-m_avg
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180, lat: 180, lon: 360)>
-array([[[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        ...,
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan]],
-
-       [[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-...
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan]],
-
-       [[       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        ...,
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan],
-        [       nan,        nan,        nan, ...,        nan,
-                nan,        nan]]], dtype=float32)
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-
-
-
lat = 50
-lon = 310
-
-m_avg.isel(lat=lat, lon=lon).plot(size=6)
-ds.tos.isel(lat=lat, lon=lon).plot()
-plt.legend(['5-month moving average', 'monthly data']);
-
-
-
-
-../../_images/b1617f641117c983408fb2f3a9bf14ab617fc2f3f74dd24f4cd5a0867cdf2a85.png -
-
-
-
-

Masking Data

-

Masking of data can be performed in Xarray by providing single or multiple conditions to either Xarray’s .where() method or a Dataset or DataArray’s .where() method. Data values matching the condition(s) are converted into a single example value, effectively masking them from the scientifically important data. In the following set of examples, we use the .where() method to mask various data values in the tos DataArray.

-

For reference, we will first print our entire sea-surface temperature (SST) dataset:

-
-
-
ds
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:    (time: 180, d2: 2, lat: 180, lon: 360)
-Coordinates:
-  * time       (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat        (lat) float64 -89.5 -88.5 -87.5 -86.5 ... 86.5 87.5 88.5 89.5
-  * lon        (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 356.5 357.5 358.5 359.5
-Dimensions without coordinates: d2
-Data variables:
-    time_bnds  (time, d2) object ...
-    lat_bnds   (lat, d2) float64 ...
-    lon_bnds   (lon, d2) float64 ...
-    tos        (time, lat, lon) float32 nan nan nan nan ... -1.746 -1.746 -1.746
-Attributes: (12/45)
-    Conventions:            CF-1.7 CMIP-6.2
-    activity_id:            CMIP
-    branch_method:          standard
-    branch_time_in_child:   674885.0
-    branch_time_in_parent:  219000.0
-    case_id:                972
-    ...                     ...
-    sub_experiment_id:      none
-    table_id:               Omon
-    tracking_id:            hdl:21.14100/2975ffd3-1d7b-47e3-961a-33f212ea4eb2
-    variable_id:            tos
-    variant_info:           CMIP6 20th century experiments (1850-2014) with C...
-    variant_label:          r11i1p1f1
-
-
-

Using where with one condition

-

In this set of examples, we are trying to analyze data at the last temporal value in the dataset. This first example illustrates the use of .isel() to perform this analysis:

-
-
-
sample = ds.tos.isel(time=-1)
-sample
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (lat: 180, lon: 360)>
-array([[      nan,       nan,       nan, ...,       nan,       nan,       nan],
-       [      nan,       nan,       nan, ...,       nan,       nan,       nan],
-       [      nan,       nan,       nan, ...,       nan,       nan,       nan],
-       ...,
-       [-1.756119, -1.756165, -1.756205, ..., -1.755922, -1.755986, -1.756058],
-       [-1.750638, -1.750658, -1.750667, ..., -1.750508, -1.750561, -1.750605],
-       [-1.74627 , -1.746267, -1.746261, ..., -1.746309, -1.746299, -1.746285]],
-      dtype=float32)
-Coordinates:
-    time     object 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-

As shown in the previous example, methods like .isel() and .sel() return data of a different shape than the original data provided to them. However, .where() preserves the shape of the original data by masking the values with a Boolean condition. Data values for which the condition is True are returned identical to the values passed in. On the other hand, data values for which the condition is False are returned as a preset example value. (This example value defaults to nan, but can be set to other values as well.)

-

Before testing .where(), it is helpful to look at the official documentation. As stated above, the .where() method takes a Boolean condition. (Boolean conditions use operators such as less-than, greater-than, and equal-to, and return a value of True or False.) Most uses of .where() check whether or not specific data values are less than or greater than a constant value. As stated in the documentation, the data values specified in the Boolean condition of .where() can be any of the following:

-
    -
  • a DataArray

  • -
  • a Dataset

  • -
  • a function

  • -
-

In the following example, we make use of .where() to mask data with temperature values greater than 0. Therefore, values greater than 0 are set to nan, as described above. (It is important to note that the Boolean condition matches values to keep, not values to mask out.)

-
-
-
masked_sample = sample.where(sample < 0.0)
-masked_sample
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (lat: 180, lon: 360)>
-array([[       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       [       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       [       nan,        nan,        nan, ...,        nan,        nan,
-               nan],
-       ...,
-       [-1.7561191, -1.7561648, -1.7562052, ..., -1.7559224, -1.7559862,
-        -1.7560585],
-       [-1.7506379, -1.7506577, -1.7506672, ..., -1.7505083, -1.750561 ,
-        -1.7506049],
-       [-1.7462697, -1.7462667, -1.7462606, ..., -1.7463093, -1.746299 ,
-        -1.7462848]], dtype=float32)
-Coordinates:
-    time     object 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-

In this example, we use Matplotlib to plot the original, unmasked data, as well as the masked data created in the previous example.

-
-
-
fig, axes = plt.subplots(ncols=2, figsize=(19, 6))
-sample.plot(ax=axes[0])
-masked_sample.plot(ax=axes[1]);
-
-
-
-
-../../_images/6d6769bc0f7eaf44e9f8d73df472fb292052c873c74f658fd8dd7864352a2619.png -
-
-
-
-

Using where with multiple conditions

-

Those familiar with Boolean conditions know that such conditions can be combined by using logical operators. In the case of .where(), the relevant logical operators are bitwise or exclusive 'and' (represented by the & symbol) and bitwise or exclusive ‘or’ (represented by the | symbol). This allows multiple masking conditions to be specified in a single use of .where(); however, be aware that if multiple conditions are specified in this way, each simple Boolean condition must be enclosed in parentheses. (If you are not familiar with Boolean conditions, or this section is confusing in any way, please review a detailed Boolean expression guide before continuing with the tutorial.) In this example, we provide multiple conditions to .where() using a more complex Boolean condition. This allows us to mask locations with temperature values less than 25, as well as locations with temperature values greater than 30. (As stated above, the Boolean condition matches values to keep, and everything else is masked out. Because we are now using more complex Boolean conditions, understanding the following example may be difficult. Please review a Boolean condition guide if needed.)

-
-
-
sample.where((sample > 25) & (sample < 30)).plot(size=6);
-
-
-
-
-../../_images/bdf1dc900425ff437651e1fbd01c7d350f664d2296806b6799b1d66f3963b5b0.png -
-
-

In addition to using DataArrays and Datasets in Boolean conditions provided to .where(), we can also use coordinate variables. In the following example, we make use of Boolean conditions containing latitude and longitude coordinates. This greatly simplifies the masking of regions outside of the Niño 3.4 region:

-

-
-
-
sample.where(
-    (sample.lat < 5) & (sample.lat > -5) & (sample.lon > 190) & (sample.lon < 240)
-).plot(size=6);
-
-
-
-
-../../_images/191a629919dca08953b893b3badbb7229c6449c29760753df90af612bb06f74a.png -
-
-
-
-

Using where with a custom fill value

-

In the previous examples that make use of .where(), the masked data values are set to nan. However, this behavior can be modified by providing a second value, in numeric form, to .where(); if this numeric value is provided, it will be used instead of nan for masked data values. In this example, masked data values are set to 0 by providing a second value of 0 to the .where() method:

-
-
-
sample.where((sample > 25) & (sample < 30), 0).plot(size=6);
-
-
-
-
-../../_images/dd9cf91449deae549bfb5900e1cd21fd6ec2b7fb47d2321bb9d7033c17209776.png -
-
-
-
-
-
-

Summary

-
    -
  • In a similar manner to NumPy arrays, performing arithmetic on a DataArray affects all values simultaneously.

  • -
  • Xarray allows for simple data aggregation, over single or multiple dimensions, by way of built-in methods such as sum() and mean().

  • -
  • Xarray supports the useful split-apply-combine workflow through the groupby method.

  • -
  • Xarray allows replacing (masking) of data matching specific Boolean conditions by means of the .where() method.

  • -
-
-

What’s next?

-

The next tutorial illustrates the use of previously covered Xarray concepts in a geoscientifically relevant example: plotting the Niño 3.4 Index.

-
-
- -
- - - - -
- - -
-
- - - - - - - - - - \ No newline at end of file diff --git a/_preview/419/core/xarray/dask-arrays-xarray.html b/_preview/419/core/xarray/dask-arrays-xarray.html deleted file mode 100644 index 43914c852..000000000 --- a/_preview/419/core/xarray/dask-arrays-xarray.html +++ /dev/null @@ -1,4839 +0,0 @@ - - - - - - - - Dask Arrays with Xarray — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - ../../_images/dask_horizontal.svg -
-

Dask Arrays with Xarray

-

The scientific Python package known as Dask provides Dask Arrays: parallel, larger-than-memory, n-dimensional arrays that make use of blocked algorithms. They are analogous to Numpy arrays, but are distributed. These terms are defined below:

-
    -
  • Parallel code uses many or all of the cores on the computer running the code.

  • -
  • Larger-than-memory refers to algorithms that break up data arrays into small pieces, operate on these pieces in an optimized fashion, and stream data from a storage device. This allows a user or programmer to work with datasets of a size larger than the available memory.

  • -
  • A blocked algorithm speeds up large computations by converting them into a series of smaller computations.

  • -
-

In this tutorial, we cover the use of Xarray to wrap Dask arrays. By using Dask arrays instead of Numpy arrays in Xarray data objects, it becomes possible to execute analysis code in parallel with much less code and effort.

-
-

Learning Objectives

-
    -
  • Learn the distinction between eager and lazy execution, and performing both types of execution with Xarray

  • -
  • Understand key features of Dask Arrays

  • -
  • Learn to perform operations with Dask Arrays in similar ways to performing operations with NumPy arrays

  • -
  • Understand the use of Xarray DataArrays and Datasets as “Dask collections”, and the use of top-level Dask functions such as dask.visualize() on such collections

  • -
  • Understand the ability to use Dask transparently in all built-in Xarray operations

  • -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Introduction to NumPy

Necessary

Familiarity with Data Arrays

Introduction to Xarray

Necessary

Familiarity with Xarray Data Structures

-
    -
  • Time to learn: 30-40 minutes

  • -
-
-
-

Imports

-

For this tutorial, as we are working with Dask, there are a number of Dask packages that must be imported. Also, this is technically an Xarray tutorial, so Xarray and NumPy must also be imported. Finally, the Pythia datasets package is imported, allowing access to the Project Pythia example data library.

-
-
-
import dask
-import dask.array as da
-import numpy as np
-import xarray as xr
-from dask.diagnostics import ProgressBar
-from dask.utils import format_bytes
-from pythia_datasets import DATASETS
-
-
-
-
-
-
-

Blocked algorithms

-

As described above, the definition of “blocked algorithm” is an algorithm that replaces a large operation with many small operations. In the case of datasets, this means that a blocked algorithm separates a dataset into chunks, and performs an operation on each.

-

As an example of how blocked algorithms work, consider a dataset containing a billion numbers, and assume that the sum of the numbers is needed. Using a non-blocked algorithm, all of the numbers are added in one operation, which is extremely inefficient. However, by using a blocked algorithm, the dataset is broken into chunks. (For the purposes of this example, assume that 1,000 chunks are created, with 1,000,000 numbers each.) The sum of the numbers in each chunk is taken, most likely in parallel, and then each of those sums are summed to obtain the final result.

-

By using blocked algorithms, we achieve the result, in this case one sum of one billion numbers, through the results of many smaller operations, in this case one thousand sums of one million numbers each. (Also note that each of the one thousand sums must then be summed, making the total number of sums 1,001.) This allows for a much greater degree of parallelism, potentially speeding up the code execution dramatically.

-
-

dask.array contains these algorithms

-

The main object type used in Dask is dask.array, which implements a subset of the ndarray (NumPy array) interface. However, unlike ndarray, dask.array uses blocked algorithms, which break up the array into smaller arrays, as described above. This allows for the execution of computations on arrays larger than memory, by using parallelism to divide the computation among multiple cores. Dask manages and coordinates blocked algorithms for any given computation by using Dask graphs, which lay out in detail the steps Dask takes to solve a problem. In addition, dask.array objects, known as Dask Arrays, are lazy; in other words, any computation performed on them is delayed until a specific method is called.

-
-
-

Create a dask.array object

-

As stated earlier, Dask Arrays are loosely based on NumPy arrays. In the next set of examples, we illustrate the main differences between Dask Arrays and NumPy arrays. In order to illustrate the differences, we must have both a Dask Array object and a NumPy array object. Therefore, this first example creates a 3-D NumPy array of random data:

-
-
-
shape = (600, 200, 200)
-arr = np.random.random(shape)
-arr
-
-
-
-
-
array([[[0.13793765, 0.01044269, 0.44404568, ..., 0.15188778,
-         0.60998641, 0.26167138],
-        [0.65410518, 0.82559389, 0.61082889, ..., 0.33756518,
-         0.96000763, 0.19669751],
-        [0.78520609, 0.00449019, 0.17333452, ..., 0.12912925,
-         0.27136132, 0.56342788],
-        ...,
-        [0.92364035, 0.24094826, 0.21244802, ..., 0.15939013,
-         0.94429789, 0.6031634 ],
-        [0.85759768, 0.32554905, 0.95713198, ..., 0.08698248,
-         0.51729638, 0.46154921],
-        [0.18183079, 0.78848845, 0.57192922, ..., 0.24232998,
-         0.18839824, 0.55457405]],
-
-       [[0.66017722, 0.01070435, 0.28667009, ..., 0.83670341,
-         0.37161382, 0.47578218],
-        [0.12740693, 0.32973988, 0.00325119, ..., 0.16816435,
-         0.52425061, 0.4474326 ],
-        [0.0750863 , 0.22922395, 0.47838793, ..., 0.23344556,
-         0.86652644, 0.29858388],
-        ...,
-        [0.93353611, 0.71800801, 0.20684877, ..., 0.25745942,
-         0.69363459, 0.08982828],
-        [0.22476884, 0.82657311, 0.6550261 , ..., 0.47195507,
-         0.53264733, 0.20368424],
-        [0.26350974, 0.65346334, 0.50773088, ..., 0.79138474,
-         0.64382108, 0.84140976]],
-
-       [[0.33846387, 0.8390845 , 0.32167848, ..., 0.6748274 ,
-         0.91663779, 0.07617983],
-        [0.94228197, 0.1851116 , 0.49044752, ..., 0.66919576,
-         0.6012342 , 0.79548039],
-        [0.35957795, 0.41851673, 0.59487082, ..., 0.46093867,
-         0.4397634 , 0.38667651],
-        ...,
-        [0.46945053, 0.26875609, 0.20811746, ..., 0.35572599,
-         0.96339126, 0.97711554],
-        [0.43409519, 0.24892858, 0.15243641, ..., 0.85494842,
-         0.47533763, 0.48944389],
-        [0.41199159, 0.63432657, 0.15945525, ..., 0.94976547,
-         0.11899054, 0.30128494]],
-
-       ...,
-
-       [[0.74454872, 0.44925685, 0.1065311 , ..., 0.68069024,
-         0.20258584, 0.14769459],
-        [0.71450335, 0.28041414, 0.59167587, ..., 0.87497222,
-         0.51722022, 0.59083561],
-        [0.02331465, 0.36487912, 0.76390505, ..., 0.41363352,
-         0.54119192, 0.67494217],
-        ...,
-        [0.46641423, 0.64522725, 0.01951141, ..., 0.15578434,
-         0.23190551, 0.65396717],
-        [0.94907865, 0.88523954, 0.26070834, ..., 0.63904801,
-         0.83170804, 0.01030226],
-        [0.22129221, 0.25007975, 0.38452656, ..., 0.73141   ,
-         0.2055071 , 0.24545829]],
-
-       [[0.20116345, 0.3597851 , 0.28976391, ..., 0.92369784,
-         0.46062284, 0.82907295],
-        [0.94476028, 0.37582999, 0.76483512, ..., 0.74104397,
-         0.25747307, 0.57368908],
-        [0.72226028, 0.12564928, 0.00595246, ..., 0.99396826,
-         0.49158381, 0.46314866],
-        ...,
-        [0.89314169, 0.91129404, 0.27245148, ..., 0.96250528,
-         0.78108175, 0.00807236],
-        [0.49392927, 0.45819755, 0.54800087, ..., 0.53189864,
-         0.14243191, 0.20478923],
-        [0.86726244, 0.55159143, 0.14999155, ..., 0.52010305,
-         0.23056443, 0.50190735]],
-
-       [[0.01637557, 0.31500913, 0.07581677, ..., 0.38953375,
-         0.11354245, 0.66564715],
-        [0.79402336, 0.8807439 , 0.7476336 , ..., 0.54182445,
-         0.04911464, 0.44197284],
-        [0.09328165, 0.67959307, 0.98257591, ..., 0.61902541,
-         0.54773258, 0.31186415],
-        ...,
-        [0.7594382 , 0.98941212, 0.20472628, ..., 0.09561932,
-         0.18042929, 0.54806322],
-        [0.49646238, 0.18435229, 0.77346261, ..., 0.14823932,
-         0.58925263, 0.94312131],
-        [0.85926267, 0.41633475, 0.36581882, ..., 0.81113106,
-         0.67105394, 0.6416953 ]]])
-
-
-
-
-
-
-
format_bytes(arr.nbytes)
-
-
-
-
-
'183.11 MiB'
-
-
-
-
-

As shown above, this NumPy array contains about 183 MB of data.

-

As stated above, we must also create a Dask Array. This next example creates a Dask Array with the same dimension sizes as the existing NumPy array:

-
-
-
darr = da.random.random(shape, chunks=(300, 100, 200))
-
-
-
-
-

By specifying values to the chunks keyword argument, we can specify the array pieces that Dask’s blocked algorithms break the array into; in this case, we specify (300, 100, 200).

-
-

Specifying Chunks

-

In this tutorial, we specify Dask Array chunks in a block shape. However, there are many additional ways to specify chunks; see this documentation for more details.

-
-
-

If you are viewing this page as a Jupyter Notebook, the next Jupyter cell will produce a rich information graphic giving in-depth details about the array and each individual chunk.

-
-
-
darr
-
-
-
-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Array Chunk
Bytes 183.11 MiB 45.78 MiB
Shape (600, 200, 200) (300, 100, 200)
Dask graph 4 chunks in 1 graph layer
Data type float64 numpy.ndarray
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 200 - 200 - 600 - -
-
-

The above graphic contains a symbolic representation of the array, including shape, dtype, and chunksize. (Your view may be different, depending on how you are accessing this page.) Notice that there is no data shown for this array; this is because Dask Arrays are lazy, as described above. Before we call a compute method for this array, we first illustrate the structure of a Dask graph. In this example, we show the Dask graph by calling .visualize() on the array:

-
-
-
darr.visualize()
-
-
-
-
-../../_images/f84e83601383c87e54c66fd6f9ec9b7603766c645d698b7adc2f3676e4661c39.png -
-
-

As shown in the above Dask graph, our array has four chunks, each one created by a call to NumPy’s “random” method (np.random.random). These chunks are concatenated into a single array after the calculation is performed.

-
-
-

Manipulate a dask.array object as you would a numpy array

-

We can perform computations on the Dask Array created above in a similar fashion to NumPy arrays. These computations include arithmetic, slicing, and reductions, among others.

-

Although the code for performing these computations is similar between NumPy arrays and Dask Arrays, the process by which they are performed is quite different. For example, it is possible to call sum() on both a NumPy array and a Dask Array; however, these two sum() calls are definitely not the same, as shown below.

-
-

What’s the difference?

-

When sum() is called on a Dask Array, the computation is not performed; instead, an expression of the computation is built. The sum() computation, as well as any other computation methods called on the same Dask Array, are not performed until a specific method (known as a compute method) is called on the array. (This is known as lazy execution.) On the other hand, calling sum() on a NumPy array performs the calculation immediately; this is known as eager execution.

-
-
-

Why the difference?

-

As described earlier, a Dask Array is divided into chunks. Any computations run on the Dask Array run on each chunk individually. If the result of the computation is obtained before the computation runs through all of the chunks, Dask can stop the computation to save CPU time and memory resources.

-

This example illustrates calling sum() on a Dask Array; it also includes a demonstration of lazy execution, as well as another Dask graph display:

-
-
-
total = darr.sum()
-total
-
-
-
-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Array Chunk
Bytes 8 B 8 B
Shape () ()
Dask graph 1 chunks in 3 graph layers
Data type float64 numpy.ndarray
-
- -
-
-
-
-
total.visualize()
-
-
-
-
-../../_images/0c416b36effb7b9b9d6818d0d0e4a56fb3406d6680b52df7ce3f36906ef429ea.png -
-
-
-
-

Compute the result

-

As described above, Dask Array objects make use of lazy execution. Therefore, operations performed on a Dask Array wait to execute until a compute method is called. As more operations are queued in this way, the Dask Array’s Dask graph increases in complexity, reflecting the steps Dask will take to perform all of the queued operations.

-

In this example, we call a compute method, simply called .compute(), to run on the Dask Array all of the stored computations:

-
-
-
%%time
-total.compute()
-
-
-
-
-
CPU times: user 403 ms, sys: 63.9 ms, total: 467 ms
-Wall time: 326 ms
-
-
-
12001299.975527769
-
-
-
-
-
-
-
-

Exercise with dask.arrays

-

In this section of the page, the examples are hands-on exercises pertaining to Dask Arrays. If these exercises are not interesting to you, this section can be used strictly as examples regardless of how the page is viewed. However, if you wish to participate in the exercises, make sure that you are viewing this page as a Jupyter Notebook.

-

For the first exercise, modify the chunk size or shape of the Dask Array created earlier. Call .sum() on the modified Dask Array, and visualize the Dask graph to view the changes.

-
-
-
da.random.random(shape, chunks=(50, 200, 400)).sum().visualize()
-
-
-
-
-../../_images/00097b2f8b8c2cd2be548a4a2abac5d778b43607c1c6229ad0d72d332cc801d1.png -
-
-

As is obvious from the above exercise, Dask quickly and easily determines a strategy for performing the operations, in this case a sum. This illustrates the appeal of Dask: automatic algorithm generation that scales from simple arithmetic problems to highly complex scientific equations with large datasets and multiple operations.

-

In this next set of examples, we demonstrate that increasing the complexity of the operations performed also increases the complexity of the Dask graph.

-

In this example, we use randomly selected functions, arguments and Python slices to create a complex set of operations. We then visualize the Dask graph to illustrate the increased complexity:

-
-
-
z = darr.dot(darr.T).mean(axis=0)[::2, :].std(axis=1)
-z
-
-
-
-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Array Chunk
Bytes 468.75 kiB 117.19 kiB
Shape (100, 600) (50, 300)
Dask graph 4 chunks in 12 graph layers
Data type float64 numpy.ndarray
-
- - - - - - - - - - - - - - - - - 600 - 100 - -
-
-
-
-
z.visualize()
-
-
-
-
-../../_images/e0e69b46b2dd1d98ddc57b525c4acf912cdc2dbad326a7f0f98a4aba6e4927a5.png -
-
-
-
-

Testing a bigger calculation

-

While the earlier examples in this tutorial described well the basics of Dask, the size of the data in those examples, about 180 MB, is far too small for an actual use of Dask.

-

In this example, we create a much larger array, more indicative of data actually used in Dask:

-
-
-
darr = da.random.random((4000, 100, 4000), chunks=(1000, 100, 500)).astype('float32')
-darr
-
-
-
-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Array Chunk
Bytes 5.96 GiB 190.73 MiB
Shape (4000, 100, 4000) (1000, 100, 500)
Dask graph 32 chunks in 2 graph layers
Data type float32 numpy.ndarray
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4000 - 100 - 4000 - -
-
-

The dataset created in the previous example is much larger, approximately 6 GB. Depending on how many programs are running on your computer, this may be greater than the amount of free RAM on your computer. However, as Dask is larger-than-memory, the amount of free RAM does not impede Dask’s ability to work on this dataset.

-

In this example, we again perform randomly selected operations, but this time on the much larger dataset. We also visualize the Dask graph, and then run the compute method. However, as computing complex functions on large datasets is inherently time-consuming, we show a progress bar to track the progress of the computation.

-
-
-
z = (darr + darr.T)[::2, :].mean(axis=2)
-
-
-
-
-
-
-
z.visualize()
-
-
-
-
-../../_images/7bf5c29e9277f3e7aa7107c746f20adbef60e1e0540948820d6944d512d9bc9e.png -
-
-
-
-
with ProgressBar():
-    computed_ds = z.compute()
-
-
-
-
-
[                                        ] | 0% Completed | 220.43 us
-
-
-
[                                        ] | 0% Completed | 107.30 ms
-
-
-
[                                        ] | 0% Completed | 208.34 ms
-
-
-
[                                        ] | 0% Completed | 309.51 ms
-
-
-
[                                        ] | 0% Completed | 412.77 ms
-
-
-
[                                        ] | 0% Completed | 515.29 ms
-
-
-
[                                        ] | 0% Completed | 616.74 ms
-
-
-
[                                        ] | 0% Completed | 717.96 ms
-
-
-
[                                        ] | 0% Completed | 818.94 ms
-
-
-
[                                        ] | 0% Completed | 922.12 ms
-
-
-
[                                        ] | 0% Completed | 1.02 s
-
-
-
[                                        ] | 0% Completed | 1.13 s
-
-
-
[                                        ] | 0% Completed | 1.24 s
-
-
-
[                                        ] | 1% Completed | 1.34 s
-
-
-
[                                        ] | 1% Completed | 1.44 s
-
-
-
[#                                       ] | 2% Completed | 1.54 s
-
-
-
[#                                       ] | 2% Completed | 1.64 s
-
-
-
[#                                       ] | 3% Completed | 1.74 s
-
-
-
[#                                       ] | 3% Completed | 1.84 s
-
-
-
[#                                       ] | 4% Completed | 1.95 s
-
-
-
[##                                      ] | 5% Completed | 2.05 s
-
-
-
[##                                      ] | 5% Completed | 2.15 s
-
-
-
[##                                      ] | 5% Completed | 2.26 s
-
-
-
[##                                      ] | 5% Completed | 2.36 s
-
-
-
[##                                      ] | 5% Completed | 2.46 s
-
-
-
[##                                      ] | 5% Completed | 2.56 s
-
-
-
[##                                      ] | 5% Completed | 2.66 s
-
-
-
[##                                      ] | 5% Completed | 2.77 s
-
-
-
[##                                      ] | 5% Completed | 2.87 s
-
-
-
[##                                      ] | 7% Completed | 2.97 s
-
-
-
[###                                     ] | 7% Completed | 3.07 s
-
-
-
[###                                     ] | 8% Completed | 3.17 s
-
-
-
[###                                     ] | 9% Completed | 3.27 s
-
-
-
[###                                     ] | 9% Completed | 3.38 s
-
-
-
[####                                    ] | 10% Completed | 3.48 s
-
-
-
[####                                    ] | 11% Completed | 3.58 s
-
-
-
[####                                    ] | 11% Completed | 3.68 s
-
-
-
[####                                    ] | 11% Completed | 3.78 s
-
-
-
[####                                    ] | 11% Completed | 3.89 s
-
-
-
[####                                    ] | 11% Completed | 3.99 s
-
-
-
[####                                    ] | 11% Completed | 4.09 s
-
-
-
[####                                    ] | 11% Completed | 4.20 s
-
-
-
[####                                    ] | 11% Completed | 4.30 s
-
-
-
[####                                    ] | 12% Completed | 4.41 s
-
-
-
[#####                                   ] | 13% Completed | 4.51 s
-
-
-
[#####                                   ] | 13% Completed | 4.61 s
-
-
-
[######                                  ] | 15% Completed | 4.72 s
-
-
-
[######                                  ] | 15% Completed | 4.82 s
-
-
-
[######                                  ] | 15% Completed | 4.93 s
-
-
-
[######                                  ] | 16% Completed | 5.04 s
-
-
-
[######                                  ] | 16% Completed | 5.14 s
-
-
-
[######                                  ] | 16% Completed | 5.24 s
-
-
-
[######                                  ] | 16% Completed | 5.34 s
-
-
-
[######                                  ] | 16% Completed | 5.44 s
-
-
-
[######                                  ] | 16% Completed | 5.55 s
-
-
-
[######                                  ] | 16% Completed | 5.65 s
-
-
-
[######                                  ] | 16% Completed | 5.75 s
-
-
-
[######                                  ] | 16% Completed | 5.85 s
-
-
-
[######                                  ] | 16% Completed | 5.95 s
-
-
-
[######                                  ] | 16% Completed | 6.05 s
-
-
-
[#######                                 ] | 18% Completed | 6.16 s
-
-
-
[#######                                 ] | 19% Completed | 6.26 s
-
-
-
[#######                                 ] | 19% Completed | 6.36 s
-
-
-
[#######                                 ] | 19% Completed | 6.46 s
-
-
-
[########                                ] | 20% Completed | 6.56 s
-
-
-
[########                                ] | 21% Completed | 6.66 s
-
-
-
[########                                ] | 21% Completed | 6.76 s
-
-
-
[########                                ] | 22% Completed | 6.86 s
-
-
-
[########                                ] | 22% Completed | 6.96 s
-
-
-
[########                                ] | 22% Completed | 7.07 s
-
-
-
[########                                ] | 22% Completed | 7.17 s
-
-
-
[########                                ] | 22% Completed | 7.27 s
-
-
-
[########                                ] | 22% Completed | 7.37 s
-
-
-
[########                                ] | 22% Completed | 7.47 s
-
-
-
[########                                ] | 22% Completed | 7.57 s
-
-
-
[#########                               ] | 23% Completed | 7.67 s
-
-
-
[#########                               ] | 23% Completed | 7.77 s
-
-
-
[#########                               ] | 24% Completed | 7.88 s
-
-
-
[##########                              ] | 25% Completed | 7.98 s
-
-
-
[##########                              ] | 25% Completed | 8.08 s
-
-
-
[##########                              ] | 26% Completed | 8.18 s
-
-
-
[###########                             ] | 27% Completed | 8.28 s
-
-
-
[###########                             ] | 27% Completed | 8.38 s
-
-
-
[###########                             ] | 27% Completed | 8.49 s
-
-
-
[###########                             ] | 27% Completed | 8.59 s
-
-
-
[###########                             ] | 27% Completed | 8.69 s
-
-
-
[###########                             ] | 27% Completed | 8.79 s
-
-
-
[###########                             ] | 27% Completed | 8.90 s
-
-
-
[###########                             ] | 27% Completed | 9.00 s
-
-
-
[###########                             ] | 28% Completed | 9.10 s
-
-
-
[###########                             ] | 29% Completed | 9.20 s
-
-
-
[############                            ] | 30% Completed | 9.30 s
-
-
-
[############                            ] | 31% Completed | 9.41 s
-
-
-
[#############                           ] | 32% Completed | 9.51 s
-
-
-
[#############                           ] | 33% Completed | 9.61 s
-
-
-
[#############                           ] | 33% Completed | 9.71 s
-
-
-
[#############                           ] | 34% Completed | 9.81 s
-
-
-
[#############                           ] | 34% Completed | 9.91 s
-
-
-
[#############                           ] | 34% Completed | 10.01 s
-
-
-
[#############                           ] | 34% Completed | 10.12 s
-
-
-
[#############                           ] | 34% Completed | 10.22 s
-
-
-
[#############                           ] | 34% Completed | 10.32 s
-
-
-
[##############                          ] | 35% Completed | 10.43 s
-
-
-
[##############                          ] | 36% Completed | 10.53 s
-
-
-
[##############                          ] | 36% Completed | 10.63 s
-
-
-
[##############                          ] | 37% Completed | 10.73 s
-
-
-
[###############                         ] | 38% Completed | 10.84 s
-
-
-
[###############                         ] | 39% Completed | 10.94 s
-
-
-
[################                        ] | 40% Completed | 11.04 s
-
-
-
[################                        ] | 41% Completed | 11.14 s
-
-
-
[################                        ] | 42% Completed | 11.24 s
-
-
-
[#################                       ] | 42% Completed | 11.34 s
-
-
-
[#################                       ] | 42% Completed | 11.45 s
-
-
-
[#################                       ] | 42% Completed | 11.55 s
-
-
-
[#################                       ] | 42% Completed | 11.65 s
-
-
-
[#################                       ] | 42% Completed | 11.75 s
-
-
-
[#################                       ] | 43% Completed | 11.86 s
-
-
-
[#################                       ] | 43% Completed | 11.96 s
-
-
-
[#################                       ] | 44% Completed | 12.06 s
-
-
-
[#################                       ] | 44% Completed | 12.16 s
-
-
-
[##################                      ] | 46% Completed | 12.26 s
-
-
-
[##################                      ] | 47% Completed | 12.36 s
-
-
-
[###################                     ] | 48% Completed | 12.46 s
-
-
-
[###################                     ] | 49% Completed | 12.56 s
-
-
-
[###################                     ] | 49% Completed | 12.67 s
-
-
-
[###################                     ] | 49% Completed | 12.77 s
-
-
-
[###################                     ] | 49% Completed | 12.87 s
-
-
-
[###################                     ] | 49% Completed | 12.97 s
-
-
-
[###################                     ] | 49% Completed | 13.07 s
-
-
-
[###################                     ] | 49% Completed | 13.17 s
-
-
-
[####################                    ] | 50% Completed | 13.27 s
-
-
-
[####################                    ] | 51% Completed | 13.37 s
-
-
-
[####################                    ] | 51% Completed | 13.47 s
-
-
-
[#####################                   ] | 53% Completed | 13.58 s
-
-
-
[#####################                   ] | 54% Completed | 13.68 s
-
-
-
[#####################                   ] | 54% Completed | 13.78 s
-
-
-
[#####################                   ] | 54% Completed | 13.88 s
-
-
-
[#####################                   ] | 54% Completed | 13.98 s
-
-
-
[#####################                   ] | 54% Completed | 14.08 s
-
-
-
[#####################                   ] | 54% Completed | 14.18 s
-
-
-
[#####################                   ] | 54% Completed | 14.28 s
-
-
-
[######################                  ] | 55% Completed | 14.38 s
-
-
-
[######################                  ] | 56% Completed | 14.48 s
-
-
-
[######################                  ] | 57% Completed | 14.59 s
-
-
-
[#######################                 ] | 58% Completed | 14.69 s
-
-
-
[#######################                 ] | 59% Completed | 14.79 s
-
-
-
[#######################                 ] | 59% Completed | 14.89 s
-
-
-
[#######################                 ] | 59% Completed | 14.99 s
-
-
-
[#######################                 ] | 59% Completed | 15.09 s
-
-
-
[#######################                 ] | 59% Completed | 15.19 s
-
-
-
[#######################                 ] | 59% Completed | 15.29 s
-
-
-
[#######################                 ] | 59% Completed | 15.39 s
-
-
-
[########################                ] | 60% Completed | 15.50 s
-
-
-
[########################                ] | 62% Completed | 15.60 s
-
-
-
[#########################               ] | 62% Completed | 15.70 s
-
-
-
[#########################               ] | 63% Completed | 15.80 s
-
-
-
[#########################               ] | 64% Completed | 15.90 s
-
-
-
[##########################              ] | 65% Completed | 16.00 s
-
-
-
[##########################              ] | 65% Completed | 16.10 s
-
-
-
[##########################              ] | 65% Completed | 16.20 s
-
-
-
[##########################              ] | 65% Completed | 16.31 s
-
-
-
[##########################              ] | 65% Completed | 16.41 s
-
-
-
[##########################              ] | 65% Completed | 16.51 s
-
-
-
[##########################              ] | 65% Completed | 16.61 s
-
-
-
[##########################              ] | 67% Completed | 16.71 s
-
-
-
[##########################              ] | 67% Completed | 16.81 s
-
-
-
[###########################             ] | 68% Completed | 16.91 s
-
-
-
[###########################             ] | 69% Completed | 17.01 s
-
-
-
[############################            ] | 70% Completed | 17.12 s
-
-
-
[############################            ] | 71% Completed | 17.22 s
-
-
-
[#############################           ] | 72% Completed | 17.32 s
-
-
-
[#############################           ] | 74% Completed | 17.42 s
-
-
-
[#############################           ] | 74% Completed | 17.52 s
-
-
-
[#############################           ] | 74% Completed | 17.62 s
-
-
-
[#############################           ] | 74% Completed | 17.72 s
-
-
-
[#############################           ] | 74% Completed | 17.82 s
-
-
-
[#############################           ] | 74% Completed | 17.92 s
-
-
-
[##############################          ] | 75% Completed | 18.03 s
-
-
-
[##############################          ] | 76% Completed | 18.13 s
-
-
-
[##############################          ] | 76% Completed | 18.23 s
-
-
-
[##############################          ] | 77% Completed | 18.33 s
-
-
-
[###############################         ] | 78% Completed | 18.43 s
-
-
-
[###############################         ] | 79% Completed | 18.53 s
-
-
-
[################################        ] | 80% Completed | 18.63 s
-
-
-
[################################        ] | 80% Completed | 18.74 s
-
-
-
[################################        ] | 80% Completed | 18.84 s
-
-
-
[################################        ] | 80% Completed | 18.94 s
-
-
-
[################################        ] | 80% Completed | 19.04 s
-
-
-
[################################        ] | 80% Completed | 19.14 s
-
-
-
[################################        ] | 81% Completed | 19.24 s
-
-
-
[################################        ] | 81% Completed | 19.34 s
-
-
-
[#################################       ] | 83% Completed | 19.45 s
-
-
-
[#################################       ] | 83% Completed | 19.55 s
-
-
-
[#################################       ] | 84% Completed | 19.65 s
-
-
-
[##################################      ] | 85% Completed | 19.75 s
-
-
-
[##################################      ] | 85% Completed | 19.85 s
-
-
-
[##################################      ] | 85% Completed | 19.96 s
-
-
-
[##################################      ] | 85% Completed | 20.06 s
-
-
-
[##################################      ] | 85% Completed | 20.16 s
-
-
-
[##################################      ] | 85% Completed | 20.26 s
-
-
-
[##################################      ] | 85% Completed | 20.37 s
-
-
-
[##################################      ] | 87% Completed | 20.47 s
-
-
-
[###################################     ] | 88% Completed | 20.57 s
-
-
-
[###################################     ] | 89% Completed | 20.67 s
-
-
-
[####################################    ] | 90% Completed | 20.77 s
-
-
-
[####################################    ] | 91% Completed | 20.88 s
-
-
-
[####################################    ] | 92% Completed | 20.98 s
-
-
-
[#####################################   ] | 93% Completed | 21.08 s
-
-
-
[#####################################   ] | 93% Completed | 21.18 s
-
-
-
[#####################################   ] | 93% Completed | 21.28 s
-
-
-
[#####################################   ] | 93% Completed | 21.38 s
-
-
-
[#####################################   ] | 93% Completed | 21.48 s
-
-
-
[#####################################   ] | 93% Completed | 21.58 s
-
-
-
[#####################################   ] | 93% Completed | 21.68 s
-
-
-
[#####################################   ] | 94% Completed | 21.79 s
-
-
-
[######################################  ] | 95% Completed | 21.89 s
-
-
-
[######################################  ] | 96% Completed | 21.99 s
-
-
-
[######################################  ] | 96% Completed | 22.09 s
-
-
-
[####################################### ] | 98% Completed | 22.19 s
-
-
-
[####################################### ] | 99% Completed | 22.29 s
-
-
-
[########################################] | 100% Completed | 22.39 s
-
-
-

-
-
-
-
-
-
-
-

Dask Arrays with Xarray

-

While directly interacting with Dask Arrays can be useful on occasion, more often than not Dask Arrays are interacted with through Xarray. Since Xarray wraps NumPy arrays, and Dask Arrays contain most of the functionality of NumPy arrays, Xarray can also wrap Dask Arrays, allowing anyone with knowledge of Xarray to easily start using the Dask interface.

-
-

Reading data with Dask and Xarray

-

As demonstrated in previous examples, a Dask Array consists of many smaller arrays, called chunks:

-
-
-
darr
-
-
-
-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Array Chunk
Bytes 5.96 GiB 190.73 MiB
Shape (4000, 100, 4000) (1000, 100, 500)
Dask graph 32 chunks in 2 graph layers
Data type float32 numpy.ndarray
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4000 - 100 - 4000 - -
-
-

As shown in the following example, to read data into Xarray as Dask Arrays, simply specify the chunks keyword argument when calling the open_dataset() function:

-
-
-
ds = xr.open_dataset(DATASETS.fetch('CESM2_sst_data.nc'), chunks={})
-ds.tos
-
-
-
-
-
/usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/xarray/conventions.py:431: SerializationWarning: variable 'tos' has multiple fill values {1e+20, 1e+20}, decoding all values to NaN.
-  new_vars[k] = decode_cf_variable(
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180, lat: 180, lon: 360)>
-dask.array<open_dataset-tos, shape=(180, 180, 360), dtype=float32, chunksize=(180, 180, 360), chunktype=numpy.ndarray>
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-

While it is a valid operation to pass an empty list to the chunks keyword argument, this technique does not specify how to chunk the data, and therefore the resulting Dask Array contains only one chunk.

-

Correct usage of the chunks keyword argument specifies how many values in each dimension are contained in a single chunk. In this example, specifying the chunks keyword argument as chunks={'time':90} indicates to Xarray and Dask that 90 time slices are allocated to each chunk on the temporal axis.

-

Since this dataset contains 180 total time slices, the data variable tos (holding the sea surface temperature data) is now split into two chunks in the temporal dimension.

-
-
-
ds = xr.open_dataset(
-    DATASETS.fetch('CESM2_sst_data.nc'),
-    engine="netcdf4",
-    chunks={"time": 90, "lat": 180, "lon": 360},
-)
-ds.tos
-
-
-
-
-
/usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/xarray/conventions.py:431: SerializationWarning: variable 'tos' has multiple fill values {1e+20, 1e+20}, decoding all values to NaN.
-  new_vars[k] = decode_cf_variable(
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (time: 180, lat: 180, lon: 360)>
-dask.array<open_dataset-tos, shape=(180, 180, 360), dtype=float32, chunksize=(90, 180, 360), chunktype=numpy.ndarray>
-Coordinates:
-  * time     (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-Attributes: (12/19)
-    cell_measures:  area: areacello
-    cell_methods:   area: mean where sea time: mean
-    comment:        Model data on the 1x1 grid includes values in all cells f...
-    description:    This may differ from "surface temperature" in regions of ...
-    frequency:      mon
-    id:             tos
-    ...             ...
-    time_label:     time-mean
-    time_title:     Temporal mean
-    title:          Sea Surface Temperature
-    type:           real
-    units:          degC
-    variable_id:    tos
-
-

It is fairly straightforward to retrieve a list of the chunks and their sizes for each dimension; simply call the .chunks method on an Xarray DataArray. In this example, we show that the tos DataArray now contains two chunks on the time dimension, with each chunk containing 90 time slices.

-
-
-
ds.tos.chunks
-
-
-
-
-
((90, 90), (180,), (360,))
-
-
-
-
-
-
-

Xarray data structures are first-class dask collections

-

If an Xarray Dataset or DataArray object uses a Dask Array, rather than a NumPy array, it counts as a first-class Dask collection. This means that you can pass such an object to dask.visualize() and dask.compute(), in the same way as an individual Dask Array.

-

In this example, we call dask.visualize on our Xarray DataArray, displaying a Dask graph for the DataArray object:

-
-
-
dask.visualize(ds)
-
-
-
-
-../../_images/c5d0c83794ccb23ae5e78a3cae32d9cc17daa71567aeea7de8ff982b35d5a53a.png -
-
-
-
-

Parallel and lazy computation using dask.array with Xarray

-

As described above, Xarray Datasets and DataArrays containing Dask Arrays are first-class Dask collections. Therefore, computations performed on such objects are deferred until a compute method is called. (This is the definition of lazy computation.)

-
-
-
z = ds.tos.mean(['lat', 'lon']).dot(ds.tos.T)
-z
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (lon: 360, lat: 180)>
-dask.array<sum-aggregate, shape=(360, 180), dtype=float32, chunksize=(360, 180), chunktype=numpy.ndarray>
-Coordinates:
-  * lat      (lat) float64 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-
-

As shown in the above example, the result of the applied operations is an Xarray DataArray that contains a Dask Array, an identical object type to the object that the operations were performed on. This is true for any operations that can be applied to Xarray DataArrays, including subsetting operations; this next example illustrates this:

-
-
-
z.isel(lat=0)
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' (lon: 360)>
-dask.array<getitem, shape=(360,), dtype=float32, chunksize=(360,), chunktype=numpy.ndarray>
-Coordinates:
-    lat      float64 -89.5
-  * lon      (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
-
-

Because the data subset created above is also a first-class Dask collection, we can view its Dask graph using the dask.visualize() function, as shown in this example:

-
-
-
dask.visualize(z)
-
-
-
-
-../../_images/922fa5c4d2d35f45f5d01ffc32579051402d6695654e0cdd9470766be3b1dce3.png -
-
-

Since this object is a first-class Dask collection, the computations performed on it have been deferred. To run these computations, we must call a compute method, in this case .compute(). This example also uses a progress bar to track the computation progress.

-
-
-
with ProgressBar():
-    computed_ds = z.compute()
-
-
-
-
-
[                                        ] | 0% Completed | 180.13 us
-
-
-
[######################                  ] | 57% Completed | 101.32 ms
-
-
-
[########################################] | 100% Completed | 202.36 ms
-
-
-

-
-
-
-
-
-
-
-
-

Summary

-

This tutorial covered the use of Xarray to access Dask Arrays, and the use of the chunks keyword argument to open datasets with Dask data instead of NumPy data. Another important concept introduced in this tutorial is the usage of Xarray Datasets and DataArrays as Dask collections, allowing Xarray objects to be manipulated in a similar manner to Dask Arrays. Finally, the concepts of larger-than-memory datasets, lazy computation, and parallel computation, and how they relate to Xarray and Dask, were covered.

-
-

Dask Shortcomings

-

Although Dask Arrays and NumPy arrays are generally interchangeable, NumPy offers some functionality that is lacking in Dask Arrays. The usage of Dask Array comes with the following relevant issues:

-
    -
  1. Operations where the resulting shape depends on the array values can produce erratic behavior, or fail altogether, when used on a Dask Array. If the operation succeeds, the resulting Dask Array will have unknown chunk sizes, which can cause other sections of code to fail.

  2. -
  3. Operations that are by nature difficult to parallelize or less useful on very large datasets, such as sort, are not included in the Dask Array interface. Some of these operations have supported versions that are inherently more intuitive to parallelize, such as topk.

  4. -
  5. Development of new Dask functionality is only initiated when such functionality is required; therefore, some lesser-used NumPy functions, such as np.sometrue, are not yet implemented in Dask. However, many of these functions can be added as community contributions, or have already been added in this manner.

  6. -
-
-
-
-

Learn More

-

For more in-depth information on Dask Arrays, visit the official documentation page. In addition, this screencast reinforces the concepts covered in this tutorial. (If you are viewing this page as a Jupyter Notebook, the screencast will appear below as an embedded YouTube video.)

-
-
-
from IPython.display import YouTubeVideo
-
-YouTubeVideo(id="9h_61hXCDuI", width=600, height=300)
-
-
-
-
-
- -
-
-
-
-

Resources and references

- -
-
- - - - -
- - -
-
- - -
- - - - - - - \ No newline at end of file diff --git a/_preview/419/core/xarray/enso-xarray.html b/_preview/419/core/xarray/enso-xarray.html deleted file mode 100644 index 3d2caacc6..000000000 --- a/_preview/419/core/xarray/enso-xarray.html +++ /dev/null @@ -1,3507 +0,0 @@ - - - - - - - - Calculating ENSO with Xarray — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Calculating ENSO with Xarray

-
-
-

Overview

-

In this tutorial, we perform and demonstrate the following tasks:

-
    -
  1. Load SST data from the CESM2 model

  2. -
  3. Mask data using .where()

  4. -
  5. Compute climatologies and anomalies using .groupby()

  6. -
  7. Use .rolling() to compute moving average

  8. -
  9. Compute, normalize, and plot the Niño 3.4 Index

  10. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Introduction to Xarray

Necessary

Computation and Masking

Necessary

-
    -
  • Time to learn: 20 minutes

  • -
-
-
-
-

Imports

-

For this tutorial, we import several Python packages. As plotting ENSO data requires a geographically accurate map, Cartopy is imported to handle geographic features and map projections. Xarray is used to manage raw data, and Matplotlib allows for feature-rich data plotting. Finally, a custom Pythia package is imported, in this case allowing access to the Pythia example data library.

-
-
-
import cartopy.crs as ccrs
-import matplotlib.pyplot as plt
-import xarray as xr
-from pythia_datasets import DATASETS
-
-
-
-
-
-
-

The Niño 3.4 Index

-

In this tutorial, we combine topics covered in previous Xarray tutorials to demonstrate a real-world example. The real-world scenario demonstrated in this tutorial is the computation of the Niño 3.4 Index, as shown in the CESM2 submission for the CMIP6 project. A rough definition of Niño 3.4, in addition to a definition of Niño data computation, is listed below:

-
-

Niño 3.4 (5N-5S, 170W-120W): The Niño 3.4 anomalies may be thought of as representing the average equatorial SSTs across the Pacific from about the dateline to the South American coast. The Niño 3.4 index typically uses a 5-month running mean, and El Niño or La Niña events are defined when the Niño 3.4 SSTs exceed +/- 0.4C for a period of six months or more.

-
-
-

Niño X Index computation: a) Compute area averaged total SST from Niño X region; b) Compute monthly climatology (e.g., 1950-1979) for area averaged total SST from Niño X region, and subtract climatology from area averaged total SST time series to obtain anomalies; c) Smooth the anomalies with a 5-month running mean; d) Normalize the smoothed values by its standard deviation over the climatological period.

-
-

-

The overall goal of this tutorial is to produce a plot of ENSO data using Xarray; this plot will resemble the Oceanic Niño Index plot shown below.

-

ONI index plot from NCAR Climate Data Guide

-

In this first example, we begin by opening datasets containing the sea-surface temperature (SST) and grid-cell size data. (These datasets are taken from the Pythia example data library, using the Pythia package imported above.) The two datasets are then combined into a single dataset using Xarray’s merge method.

-
-
-
filepath = DATASETS.fetch('CESM2_sst_data.nc')
-data = xr.open_dataset(filepath)
-filepath2 = DATASETS.fetch('CESM2_grid_variables.nc')
-areacello = xr.open_dataset(filepath2).areacello
-
-ds = xr.merge([data, areacello])
-ds
-
-
-
-
-
/usr/share/miniconda3/envs/pythia-book-dev/lib/python3.10/site-packages/xarray/conventions.py:431: SerializationWarning: variable 'tos' has multiple fill values {1e+20, 1e+20}, decoding all values to NaN.
-  new_vars[k] = decode_cf_variable(
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:    (time: 180, d2: 2, lat: 180, lon: 360)
-Coordinates:
-  * time       (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat        (lat) float64 -89.5 -88.5 -87.5 -86.5 ... 86.5 87.5 88.5 89.5
-  * lon        (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 356.5 357.5 358.5 359.5
-Dimensions without coordinates: d2
-Data variables:
-    time_bnds  (time, d2) object ...
-    lat_bnds   (lat, d2) float64 ...
-    lon_bnds   (lon, d2) float64 ...
-    tos        (time, lat, lon) float32 ...
-    areacello  (lat, lon) float64 ...
-Attributes: (12/45)
-    Conventions:            CF-1.7 CMIP-6.2
-    activity_id:            CMIP
-    branch_method:          standard
-    branch_time_in_child:   674885.0
-    branch_time_in_parent:  219000.0
-    case_id:                972
-    ...                     ...
-    sub_experiment_id:      none
-    table_id:               Omon
-    tracking_id:            hdl:21.14100/2975ffd3-1d7b-47e3-961a-33f212ea4eb2
-    variable_id:            tos
-    variant_info:           CMIP6 20th century experiments (1850-2014) with C...
-    variant_label:          r11i1p1f1
-
-

This example uses Matplotlib and Cartopy to plot the first time slice of the dataset on an actual geographic map. By doing so, we verify that the data values fit the pattern of SST data:

-
-
-
fig = plt.figure(figsize=(12, 6))
-ax = plt.axes(projection=ccrs.Robinson(central_longitude=180))
-ax.coastlines()
-ax.gridlines()
-ds.tos.isel(time=0).plot(
-    ax=ax, transform=ccrs.PlateCarree(), vmin=-2, vmax=30, cmap='coolwarm'
-);
-
-
-
-
-../../_images/0625e346565833288d173c5d356f8fe736cf6f11ee8f2d22dfd533daea8e149d.png -
-
-
-
-

Select the Niño 3.4 region

-

In this set of examples, we demonstrate the selection of data values from a dataset which are located in the Niño 3.4 geographic region. The following example illustrates a selection technique that uses the sel() or isel() method:

-
-
-
tos_nino34 = ds.sel(lat=slice(-5, 5), lon=slice(190, 240))
-tos_nino34
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:    (time: 180, d2: 2, lat: 10, lon: 50)
-Coordinates:
-  * time       (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat        (lat) float64 -4.5 -3.5 -2.5 -1.5 -0.5 0.5 1.5 2.5 3.5 4.5
-  * lon        (lon) float64 190.5 191.5 192.5 193.5 ... 236.5 237.5 238.5 239.5
-Dimensions without coordinates: d2
-Data variables:
-    time_bnds  (time, d2) object ...
-    lat_bnds   (lat, d2) float64 ...
-    lon_bnds   (lon, d2) float64 ...
-    tos        (time, lat, lon) float32 ...
-    areacello  (lat, lon) float64 ...
-Attributes: (12/45)
-    Conventions:            CF-1.7 CMIP-6.2
-    activity_id:            CMIP
-    branch_method:          standard
-    branch_time_in_child:   674885.0
-    branch_time_in_parent:  219000.0
-    case_id:                972
-    ...                     ...
-    sub_experiment_id:      none
-    table_id:               Omon
-    tracking_id:            hdl:21.14100/2975ffd3-1d7b-47e3-961a-33f212ea4eb2
-    variable_id:            tos
-    variant_info:           CMIP6 20th century experiments (1850-2014) with C...
-    variant_label:          r11i1p1f1
-
-

This example illustrates the alternate technique for selecting Niño 3.4 data, which makes use of the where() method:

-
-
-
tos_nino34 = ds.where(
-    (ds.lat < 5) & (ds.lat > -5) & (ds.lon > 190) & (ds.lon < 240), drop=True
-)
-tos_nino34
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:    (time: 180, d2: 2, lat: 10, lon: 50)
-Coordinates:
-  * time       (time) object 2000-01-15 12:00:00 ... 2014-12-15 12:00:00
-  * lat        (lat) float64 -4.5 -3.5 -2.5 -1.5 -0.5 0.5 1.5 2.5 3.5 4.5
-  * lon        (lon) float64 190.5 191.5 192.5 193.5 ... 236.5 237.5 238.5 239.5
-Dimensions without coordinates: d2
-Data variables:
-    time_bnds  (time, d2, lat, lon) object 2000-01-01 00:00:00 ... 2015-01-01...
-    lat_bnds   (lat, d2, lon) float64 -5.0 -5.0 -5.0 -5.0 ... 5.0 5.0 5.0 5.0
-    lon_bnds   (lon, d2, lat) float64 190.0 190.0 190.0 ... 240.0 240.0 240.0
-    tos        (time, lat, lon) float32 28.26 28.16 28.06 ... 28.54 28.57 28.63
-    areacello  (lat, lon) float64 1.233e+10 1.233e+10 ... 1.233e+10 1.233e+10
-Attributes: (12/45)
-    Conventions:            CF-1.7 CMIP-6.2
-    activity_id:            CMIP
-    branch_method:          standard
-    branch_time_in_child:   674885.0
-    branch_time_in_parent:  219000.0
-    case_id:                972
-    ...                     ...
-    sub_experiment_id:      none
-    table_id:               Omon
-    tracking_id:            hdl:21.14100/2975ffd3-1d7b-47e3-961a-33f212ea4eb2
-    variable_id:            tos
-    variant_info:           CMIP6 20th century experiments (1850-2014) with C...
-    variant_label:          r11i1p1f1
-
-

Finally, we plot the selected region to ensure it fits the definition of the Niño 3.4 region:

-
-
-
fig = plt.figure(figsize=(12, 6))
-ax = plt.axes(projection=ccrs.Robinson(central_longitude=180))
-ax.coastlines()
-ax.gridlines()
-tos_nino34.tos.isel(time=0).plot(
-    ax=ax, transform=ccrs.PlateCarree(), vmin=-2, vmax=30, cmap='coolwarm'
-)
-ax.set_extent((120, 300, 10, -10))
-
-
-
-
-../../_images/f9037e8013e3e2b21587a371853bb666704b3c8c4de15e6827c9ba22b871d1ba.png -
-
-
-
-

Compute the anomalies

-

There are three main steps to obtain the anomalies from the Niño 3.4 dataset created in the previous set of examples. First, we use the groupby() method to convert to monthly data. Second, we subtract the mean sea-surface temperature (SST) from the monthly data. Finally, we obtain the anomalies by computing a weighted average. These steps are illustrated in the next example:

-
-
-
gb = tos_nino34.tos.groupby('time.month')
-tos_nino34_anom = gb - gb.mean(dim='time')
-index_nino34 = tos_nino34_anom.weighted(tos_nino34.areacello).mean(dim=['lat', 'lon'])
-
-
-
-
-

In this example, we smooth the data curve by applying a mean function with a 5-month moving window to the anomaly dataset. We then plot the smoothed data against the original data to demonstrate:

-
-
-
index_nino34_rolling_mean = index_nino34.rolling(time=5, center=True).mean()
-
-
-
-
-
-
-
index_nino34.plot(size=8)
-index_nino34_rolling_mean.plot()
-plt.legend(['anomaly', '5-month running mean anomaly'])
-plt.title('SST anomaly over the Niño 3.4 region');
-
-
-
-
-../../_images/4f37aea72431e50f7fe927d25944e7b69bbe6eeb3cd0e8353eb442ab8fc4553c.png -
-
-

Since the ENSO index conveys deviations from a norm, the calculation of Niño data requires a standard deviation. In this example, we calculate the standard deviation of the SST in the Niño 3.4 region data, across the entire time period of the data array:

-
-
-
std_dev = tos_nino34.tos.std()
-std_dev
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'tos' ()>
-array(1.8436452, dtype=float32)
-
-

The final step of the Niño 3.4 index calculation involves normalizing the data. In this example, we perform this normalization by dividing the smoothed anomaly data by the standard deviation calculated above:

-
-
-
normalized_index_nino34_rolling_mean = index_nino34_rolling_mean / std_dev
-
-
-
-
-
-
-

Visualize the computed Niño 3.4 index

-

In this example, we use Matplotlib to generate a plot of our final Niño 3.4 data. This plot is set up to highlight values above 0.5, corresponding to El Niño (warm) events, and values below -0.5, corresponding to La Niña (cold) events.

-
-
-
fig = plt.figure(figsize=(12, 6))
-
-plt.fill_between(
-    normalized_index_nino34_rolling_mean.time.data,
-    normalized_index_nino34_rolling_mean.where(
-        normalized_index_nino34_rolling_mean >= 0.4
-    ).data,
-    0.4,
-    color='red',
-    alpha=0.9,
-)
-plt.fill_between(
-    normalized_index_nino34_rolling_mean.time.data,
-    normalized_index_nino34_rolling_mean.where(
-        normalized_index_nino34_rolling_mean <= -0.4
-    ).data,
-    -0.4,
-    color='blue',
-    alpha=0.9,
-)
-
-normalized_index_nino34_rolling_mean.plot(color='black')
-plt.axhline(0, color='black', lw=0.5)
-plt.axhline(0.4, color='black', linewidth=0.5, linestyle='dotted')
-plt.axhline(-0.4, color='black', linewidth=0.5, linestyle='dotted')
-plt.title('Niño 3.4 Index');
-
-
-
-
-../../_images/dc80aaee468d8d4a69860c573d1632781902fc13e95344e4d357dcd1f01b2370.png -
-
-
-
-
-

Summary

-

This tutorial covered the use of Xarray features, including selection, grouping, and statistical functions, to compute and visualize a data index important to climate science.

-
-
-

Resources and References

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/core/xarray/xarray-intro.html b/_preview/419/core/xarray/xarray-intro.html deleted file mode 100644 index 7abd8d7ba..000000000 --- a/_preview/419/core/xarray/xarray-intro.html +++ /dev/null @@ -1,10969 +0,0 @@ - - - - - - - - Introduction to Xarray — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
- - -
- -
- On this page -
- -
-
-
-
-
- -
- -

xarray Logo

-
-

Introduction to Xarray

-
-
-

Overview

-

The examples in this tutorial focus on the fundamentals of working with gridded, labeled data using Xarray. Xarray works by introducing additional abstractions into otherwise ordinary data arrays. In this tutorial, we demonstrate the usefulness of these abstractions. The examples in this tutorial explain how the proper usage of Xarray abstractions generally leads to simpler, more robust code.

-

The following topics will be covered in this tutorial:

-
    -
  1. Create a DataArray, one of the core object types in Xarray

  2. -
  3. Understand how to use named coordinates and metadata in a DataArray

  4. -
  5. Combine individual DataArrays into a Dataset, the other core object type in Xarray

  6. -
  7. Subset, slice, and interpolate the data using named coordinates

  8. -
  9. Open netCDF data using Xarray

  10. -
  11. Basic subsetting and aggregation of a Dataset

  12. -
  13. Brief introduction to plotting with Xarray

  14. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

NumPy Basics

Necessary

Intermediate NumPy

Helpful

Familiarity with indexing and slicing arrays

NumPy Broadcasting

Helpful

Familiarity with array arithmetic and broadcasting

Introduction to Pandas

Helpful

Familiarity with labeled data

Datetime

Helpful

Familiarity with time formats and the timedelta object

Understanding of NetCDF

Helpful

Familiarity with metadata structure

-
    -
  • Time to learn: 40 minutes

  • -
-
-
-
-

Imports

-

In earlier tutorials, we explained the abbreviation of commonly used scientific Python package names in import statements. Just as numpy is abbreviated np, and just as pandas is abbreviated pd, the name xarray is often abbreviated xr in import statements. In addition, we also import pythia_datasets, which provides sample data used in these examples.

-
-
-
from datetime import timedelta
-
-import numpy as np
-import pandas as pd
-import xarray as xr
-from pythia_datasets import DATASETS
-
-
-
-
-
-
-

Introducing the DataArray and Dataset

-

As stated in earlier tutorials, NumPy arrays contain many useful features, making NumPy an essential part of the scientific Python stack. Xarray expands on these features, adding streamlined data manipulation capabilities. These capabilities are similar to those provided by Pandas, except that they are focused on gridded N-dimensional data instead of tabular data. Its interface is based largely on the netCDF data model (variables, attributes, and dimensions), but it goes beyond the traditional netCDF interfaces in order to provide additional useful functionality, similar to netCDF-java’s Common Data Model (CDM).

-
-

Creation of a DataArray object

-

The DataArray in one of the most basic elements of Xarray; a DataArray object is similar to a numpy ndarray object. (For more information, see the documentation here.) In addition to retaining most functionality from NumPy arrays, Xarray DataArrays provide two critical pieces of functionality:

-
    -
  1. Coordinate names and values are stored with the data, making slicing and indexing much more powerful.

  2. -
  3. Attributes, similar to those in netCDF files, can be stored in a container built into the DataArray.

  4. -
-

In these examples, we create a NumPy array, and use it as a wrapper for a new DataArray object; we then explore some properties of a DataArray.

-
-

Generate a random numpy array

-

In this first example, we create a numpy array, holding random placeholder data of temperatures in Kelvin:

-
-
-
data = 283 + 5 * np.random.randn(5, 3, 4)
-data
-
-
-
-
-
array([[[288.17453499, 276.70400275, 281.04687463, 281.83912253],
-        [287.94159739, 275.22335944, 284.83358678, 288.45280232],
-        [285.37081671, 289.79919438, 283.68341595, 283.51743547]],
-
-       [[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-        [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-        [280.81123005, 291.60986448, 282.50638476, 273.47615803]],
-
-       [[277.56902413, 276.85323814, 281.81861453, 285.12986349],
-        [286.36898602, 283.2409484 , 284.50584781, 279.2714556 ],
-        [290.2675743 , 287.54335946, 280.44059571, 272.20073714]],
-
-       [[282.24629477, 280.91947957, 288.2818906 , 282.04971994],
-        [280.18176496, 281.91725667, 281.70153863, 285.55123859],
-        [284.98897599, 283.87297653, 282.2578214 , 276.69365239]],
-
-       [[287.26828592, 288.89220492, 276.78387873, 288.2794351 ],
-        [279.63600649, 287.92811451, 286.82669327, 284.9137281 ],
-        [281.10191176, 281.22098533, 285.36948685, 286.56486298]]])
-
-
-
-
-
-
-

Wrap the array: first attempt

-

For our first attempt at wrapping a NumPy array into a DataArray, we simply use the DataArray method of Xarray, passing the NumPy array we just created:

-
-
-
temp = xr.DataArray(data)
-temp
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (dim_0: 5, dim_1: 3, dim_2: 4)>
-array([[[288.17453499, 276.70400275, 281.04687463, 281.83912253],
-        [287.94159739, 275.22335944, 284.83358678, 288.45280232],
-        [285.37081671, 289.79919438, 283.68341595, 283.51743547]],
-
-       [[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-        [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-        [280.81123005, 291.60986448, 282.50638476, 273.47615803]],
-
-       [[277.56902413, 276.85323814, 281.81861453, 285.12986349],
-        [286.36898602, 283.2409484 , 284.50584781, 279.2714556 ],
-        [290.2675743 , 287.54335946, 280.44059571, 272.20073714]],
-
-       [[282.24629477, 280.91947957, 288.2818906 , 282.04971994],
-        [280.18176496, 281.91725667, 281.70153863, 285.55123859],
-        [284.98897599, 283.87297653, 282.2578214 , 276.69365239]],
-
-       [[287.26828592, 288.89220492, 276.78387873, 288.2794351 ],
-        [279.63600649, 287.92811451, 286.82669327, 284.9137281 ],
-        [281.10191176, 281.22098533, 285.36948685, 286.56486298]]])
-Dimensions without coordinates: dim_0, dim_1, dim_2
-
-

Note two things:

-
    -
  1. Since NumPy arrays have no dimension names, our new DataArray takes on placeholder dimension names, in this case dim_0, dim_1, and dim_2. In our next example, we demonstrate how to add more meaningful dimension names.

  2. -
  3. If you are viewing this page as a Jupyter Notebook, running the above example generates a rich display of the data contained in our DataArray. This display comes with many ways to explore the data; for example, clicking the array symbol expands or collapses the data view.

  4. -
-
-
-

Assign dimension names

-

Much of the power of Xarray comes from making use of named dimensions. In order to make full use of this, we need to provide more useful dimension names. We can generate these names when creating a DataArray by passing an ordered list of names to the DataArray method, using the keyword argument dims:

-
-
-
temp = xr.DataArray(data, dims=['time', 'lat', 'lon'])
-temp
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
-array([[[288.17453499, 276.70400275, 281.04687463, 281.83912253],
-        [287.94159739, 275.22335944, 284.83358678, 288.45280232],
-        [285.37081671, 289.79919438, 283.68341595, 283.51743547]],
-
-       [[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-        [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-        [280.81123005, 291.60986448, 282.50638476, 273.47615803]],
-
-       [[277.56902413, 276.85323814, 281.81861453, 285.12986349],
-        [286.36898602, 283.2409484 , 284.50584781, 279.2714556 ],
-        [290.2675743 , 287.54335946, 280.44059571, 272.20073714]],
-
-       [[282.24629477, 280.91947957, 288.2818906 , 282.04971994],
-        [280.18176496, 281.91725667, 281.70153863, 285.55123859],
-        [284.98897599, 283.87297653, 282.2578214 , 276.69365239]],
-
-       [[287.26828592, 288.89220492, 276.78387873, 288.2794351 ],
-        [279.63600649, 287.92811451, 286.82669327, 284.9137281 ],
-        [281.10191176, 281.22098533, 285.36948685, 286.56486298]]])
-Dimensions without coordinates: time, lat, lon
-
-

This DataArray is already an improvement over a NumPy array; the DataArray contains names for each of the dimensions (or axes in NumPy parlance). An additional improvement is the association of coordinate-value arrays with data upon creation of a DataArray. In the next example, we illustrate the creation of NumPy arrays representing the coordinate values for each dimension of the DataArray, and how to associate these coordinate arrays with the data in our DataArray.

-
-
-
-

Create a DataArray with named Coordinates

-
-

Make time and space coordinates

-

In this example, we use Pandas to create an array of datetime data. This array will be used in a later example to add a named coordinate, called time, to a DataArray.

-
-
-
times = pd.date_range('2018-01-01', periods=5)
-times
-
-
-
-
-
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
-               '2018-01-05'],
-              dtype='datetime64[ns]', freq='D')
-
-
-
-
-

Before associating coordinates with our DataArray, we must also create latitude and longitude coordinate arrays. In these examples, we use placeholder data, and create the arrays in NumPy format:

-
-
-
lons = np.linspace(-120, -60, 4)
-lats = np.linspace(25, 55, 3)
-
-
-
-
-
-
-

Initialize the DataArray with complete coordinate info

-

In this example, we create a new DataArray. Similar to an earlier example, we use the dims keyword argument to specify the dimension names; however, in this case, we also specify the coordinate arrays using the coords keyword argument:

-
-
-
temp = xr.DataArray(data, coords=[times, lats, lons], dims=['time', 'lat', 'lon'])
-temp
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
-array([[[288.17453499, 276.70400275, 281.04687463, 281.83912253],
-        [287.94159739, 275.22335944, 284.83358678, 288.45280232],
-        [285.37081671, 289.79919438, 283.68341595, 283.51743547]],
-
-       [[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-        [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-        [280.81123005, 291.60986448, 282.50638476, 273.47615803]],
-
-       [[277.56902413, 276.85323814, 281.81861453, 285.12986349],
-        [286.36898602, 283.2409484 , 284.50584781, 279.2714556 ],
-        [290.2675743 , 287.54335946, 280.44059571, 272.20073714]],
-
-       [[282.24629477, 280.91947957, 288.2818906 , 282.04971994],
-        [280.18176496, 281.91725667, 281.70153863, 285.55123859],
-        [284.98897599, 283.87297653, 282.2578214 , 276.69365239]],
-
-       [[287.26828592, 288.89220492, 276.78387873, 288.2794351 ],
-        [279.63600649, 287.92811451, 286.82669327, 284.9137281 ],
-        [281.10191176, 281.22098533, 285.36948685, 286.56486298]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-
-
-
-

Set useful attributes

-

As described above, DataArrays have a built-in container for attribute metadata. These attributes are similar to those in netCDF files, and are added to a DataArray using its attrs method:

-
-
-
temp.attrs['units'] = 'kelvin'
-temp.attrs['standard_name'] = 'air_temperature'
-
-temp
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
-array([[[288.17453499, 276.70400275, 281.04687463, 281.83912253],
-        [287.94159739, 275.22335944, 284.83358678, 288.45280232],
-        [285.37081671, 289.79919438, 283.68341595, 283.51743547]],
-
-       [[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-        [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-        [280.81123005, 291.60986448, 282.50638476, 273.47615803]],
-
-       [[277.56902413, 276.85323814, 281.81861453, 285.12986349],
-        [286.36898602, 283.2409484 , 284.50584781, 279.2714556 ],
-        [290.2675743 , 287.54335946, 280.44059571, 272.20073714]],
-
-       [[282.24629477, 280.91947957, 288.2818906 , 282.04971994],
-        [280.18176496, 281.91725667, 281.70153863, 285.55123859],
-        [284.98897599, 283.87297653, 282.2578214 , 276.69365239]],
-
-       [[287.26828592, 288.89220492, 276.78387873, 288.2794351 ],
-        [279.63600649, 287.92811451, 286.82669327, 284.9137281 ],
-        [281.10191176, 281.22098533, 285.36948685, 286.56486298]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-
-
-

Issues with preservation of attributes

-

In this example, we illustrate an important concept relating to attributes. When a mathematical operation is performed on a DataArray, all of the coordinate arrays remain attached to the DataArray, but any attribute metadata assigned is lost. Attributes are removed in this way due to the fact that they may not convey correct or appropriate metadata after an arbitrary arithmetic operation.

-

This example converts our DataArray values from Kelvin to degrees Celsius. Pay attention to the attributes in the Jupyter rich display below. (If you are not viewing this page as a Jupyter notebook, see the Xarray documentation to learn how to display the attributes.)

-
-
-
temp_in_celsius = temp - 273.15
-temp_in_celsius
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
-array([[[15.02453499,  3.55400275,  7.89687463,  8.68912253],
-        [14.79159739,  2.07335944, 11.68358678, 15.30280232],
-        [12.22081671, 16.64919438, 10.53341595, 10.36743547]],
-
-       [[14.18312259,  2.41234131,  1.92276144, 14.2268635 ],
-        [11.79226746,  9.88580664, 15.6263213 , 10.99675698],
-        [ 7.66123005, 18.45986448,  9.35638476,  0.32615803]],
-
-       [[ 4.41902413,  3.70323814,  8.66861453, 11.97986349],
-        [13.21898602, 10.0909484 , 11.35584781,  6.1214556 ],
-        [17.1175743 , 14.39335946,  7.29059571, -0.94926286]],
-
-       [[ 9.09629477,  7.76947957, 15.1318906 ,  8.89971994],
-        [ 7.03176496,  8.76725667,  8.55153863, 12.40123859],
-        [11.83897599, 10.72297653,  9.1078214 ,  3.54365239]],
-
-       [[14.11828592, 15.74220492,  3.63387873, 15.1294351 ],
-        [ 6.48600649, 14.77811451, 13.67669327, 11.7637281 ],
-        [ 7.95191176,  8.07098533, 12.21948685, 13.41486298]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-
-

In addition, if you need more details on how Xarray handles metadata, you can review this documentation page.

-
-
-
-

The Dataset: a container for DataArrays with shared coordinates

-

Along with the DataArray, the other main object type in Xarray is the Dataset. Datasets are containers similar to Python dictionaries; each Dataset can hold one or more DataArrays. In addition, the DataArrays contained in a Dataset can share coordinates, although this behavior is optional. (For more information, see the official documentation page.)

-

Dataset objects are most often created by loading data from a data file. We will cover this functionality in a later example; in this example, we will create a Dataset from two DataArrays. We will use our existing temperature DataArray for one of these DataArrays; the other one is created in the next example.

-

In addition, both of these DataArrays will share coordinate axes. Therefore, the next example will also illustrate the usage of common coordinate axes across DataArrays in a Dataset.

-
-

Create a pressure DataArray using the same coordinates

-

In this example, we create a DataArray object to hold pressure data. This new DataArray is set up in a very similar fashion to the temperature DataArray created above.

-
-
-
pressure_data = 1000.0 + 5 * np.random.randn(5, 3, 4)
-pressure = xr.DataArray(
-    pressure_data, coords=[times, lats, lons], dims=['time', 'lat', 'lon']
-)
-pressure.attrs['units'] = 'hPa'
-pressure.attrs['standard_name'] = 'air_pressure'
-
-pressure
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
-array([[[ 997.32026105, 1008.03334709, 1002.00218808, 1006.92729241],
-        [1001.47384984,  998.28214278, 1000.1369238 , 1003.70249478],
-        [1000.14164393, 1001.66897953, 1011.21372499, 1006.39646915]],
-
-       [[1002.35721661, 1002.87659525, 1005.02690344,  988.67224694],
-        [1007.32831662,  999.1231055 , 1004.75546285,  998.05877238],
-        [1000.17565215, 1006.46744436, 1004.54684991, 1001.45552845]],
-
-       [[ 997.51080706,  994.77862152,  998.17388164,  999.55009856],
-        [ 989.32744402,  993.91390712,  999.21020959,  996.67918356],
-        [ 996.26115286, 1002.53049864,  997.08790402,  995.54331325]],
-
-       [[ 997.55585648,  996.90756224, 1004.27762224, 1001.13485627],
-        [ 995.56184846,  998.83640061,  999.94086729,  995.12531582],
-        [1004.04641359, 1003.86895349,  996.57564834, 1012.32818876]],
-
-       [[ 995.18181589, 1001.74444414,  995.91737213, 1011.79815935],
-        [ 992.90637799,  998.4358763 , 1011.79952775,  996.83968292],
-        [ 994.4360935 , 1000.99273445, 1013.34335343, 1002.9177293 ]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          hPa
-    standard_name:  air_pressure
-
-
-
-

Create a Dataset object

-

Before we can create a Dataset object, we must first name each of the DataArray objects that will be added to the new Dataset.

-

To name the DataArrays that will be added to our Dataset, we can set up a Python dictionary as shown in the next example. We can then pass this dictionary to the Dataset method using the keyword argument data_vars; this creates a new Dataset containing both of our DataArrays.

-
-
-
ds = xr.Dataset(data_vars={'Temperature': temp, 'Pressure': pressure})
-ds
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:      (time: 5, lat: 3, lon: 4)
-Coordinates:
-  * time         (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat          (lat) float64 25.0 40.0 55.0
-  * lon          (lon) float64 -120.0 -100.0 -80.0 -60.0
-Data variables:
-    Temperature  (time, lat, lon) float64 288.2 276.7 281.0 ... 285.4 286.6
-    Pressure     (time, lat, lon) float64 997.3 1.008e+03 ... 1.003e+03
-
-

As listed in the rich display above, the new Dataset object is aware that both DataArrays share the same coordinate axes. (Please note that if this page is not run as a Jupyter Notebook, the rich display may be unavailable.)

-
-
-

Access Data variables and Coordinates in a Dataset

-

This set of examples illustrates different methods for retrieving DataArrays from a Dataset.

-

This first example shows how to retrieve DataArrays using the “dot” notation:

-
-
-
ds.Pressure
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'Pressure' (time: 5, lat: 3, lon: 4)>
-array([[[ 997.32026105, 1008.03334709, 1002.00218808, 1006.92729241],
-        [1001.47384984,  998.28214278, 1000.1369238 , 1003.70249478],
-        [1000.14164393, 1001.66897953, 1011.21372499, 1006.39646915]],
-
-       [[1002.35721661, 1002.87659525, 1005.02690344,  988.67224694],
-        [1007.32831662,  999.1231055 , 1004.75546285,  998.05877238],
-        [1000.17565215, 1006.46744436, 1004.54684991, 1001.45552845]],
-
-       [[ 997.51080706,  994.77862152,  998.17388164,  999.55009856],
-        [ 989.32744402,  993.91390712,  999.21020959,  996.67918356],
-        [ 996.26115286, 1002.53049864,  997.08790402,  995.54331325]],
-
-       [[ 997.55585648,  996.90756224, 1004.27762224, 1001.13485627],
-        [ 995.56184846,  998.83640061,  999.94086729,  995.12531582],
-        [1004.04641359, 1003.86895349,  996.57564834, 1012.32818876]],
-
-       [[ 995.18181589, 1001.74444414,  995.91737213, 1011.79815935],
-        [ 992.90637799,  998.4358763 , 1011.79952775,  996.83968292],
-        [ 994.4360935 , 1000.99273445, 1013.34335343, 1002.9177293 ]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          hPa
-    standard_name:  air_pressure
-
-

In addition, you can access DataArrays through a dictionary syntax, as shown in this example:

-
-
-
ds['Pressure']
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'Pressure' (time: 5, lat: 3, lon: 4)>
-array([[[ 997.32026105, 1008.03334709, 1002.00218808, 1006.92729241],
-        [1001.47384984,  998.28214278, 1000.1369238 , 1003.70249478],
-        [1000.14164393, 1001.66897953, 1011.21372499, 1006.39646915]],
-
-       [[1002.35721661, 1002.87659525, 1005.02690344,  988.67224694],
-        [1007.32831662,  999.1231055 , 1004.75546285,  998.05877238],
-        [1000.17565215, 1006.46744436, 1004.54684991, 1001.45552845]],
-
-       [[ 997.51080706,  994.77862152,  998.17388164,  999.55009856],
-        [ 989.32744402,  993.91390712,  999.21020959,  996.67918356],
-        [ 996.26115286, 1002.53049864,  997.08790402,  995.54331325]],
-
-       [[ 997.55585648,  996.90756224, 1004.27762224, 1001.13485627],
-        [ 995.56184846,  998.83640061,  999.94086729,  995.12531582],
-        [1004.04641359, 1003.86895349,  996.57564834, 1012.32818876]],
-
-       [[ 995.18181589, 1001.74444414,  995.91737213, 1011.79815935],
-        [ 992.90637799,  998.4358763 , 1011.79952775,  996.83968292],
-        [ 994.4360935 , 1000.99273445, 1013.34335343, 1002.9177293 ]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          hPa
-    standard_name:  air_pressure
-
-

Dataset objects are mainly used for loading data from files, which will be covered later in this tutorial.

-
-
-
-
-

Subsetting and selection by coordinate values

-

Much of the power of labeled coordinates comes from the ability to select data based on coordinate names and values instead of array indices. This functionality will be covered on a basic level in these examples. (Later tutorials will cover this topic in much greater detail.)

-
-

NumPy-like selection

-

In these examples, we are trying to extract all of our spatial data for a single date; in this case, January 2, 2018. For our first example, we retrieve spatial data using index selection, as with a NumPy array:

-
-
-
indexed_selection = temp[1, :, :]  # Index 1 along axis 0 is the time slice we want...
-indexed_selection
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (lat: 3, lon: 4)>
-array([[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-       [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-       [280.81123005, 291.60986448, 282.50638476, 273.47615803]])
-Coordinates:
-    time     datetime64[ns] 2018-01-02
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-

This example reveals one of the major shortcomings of index selection. In order to retrieve the correct data using index selection, anyone using a DataArray must have precise knowledge of the axes in the DataArray, including the order of the axes and the meaning of their indices.

-

By using named coordinates, as shown in the next set of examples, we can avoid this cumbersome burden.

-
-
-

Selecting with .sel()

-

In this example, we show how to select data based on coordinate values, by way of the .sel() method. This method takes one or more named coordinates in keyword-argument format, and returns data matching the coordinates.

-
-
-
named_selection = temp.sel(time='2018-01-02')
-named_selection
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (lat: 3, lon: 4)>
-array([[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-       [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-       [280.81123005, 291.60986448, 282.50638476, 273.47615803]])
-Coordinates:
-    time     datetime64[ns] 2018-01-02
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-

This method yields the same result as the index selection, however:

-
    -
  • we didn’t have to know anything about how the array was created or stored

  • -
  • our code is agnostic about how many dimensions we are dealing with

  • -
  • the intended meaning of our code is much clearer

  • -
-
-
-

Approximate selection and interpolation

-

When working with temporal and spatial data, it is a common practice to sample data close to the coordinate points in a dataset. The following set of examples illustrates some common techniques for this practice.

-
-

Nearest-neighbor sampling

-

In this example, we are trying to sample a temporal data point within 2 days of the date 2018-01-07. Since the final date on our DataArray’s temporal axis is 2018-01-05, this is an appropriate problem.

-

We can use the .sel() method to perform nearest-neighbor sampling, by setting the method keyword argument to ‘nearest’. We can also optionally provide a tolerance argument; with temporal data, this is a timedelta object.

-
-
-
temp.sel(time='2018-01-07', method='nearest', tolerance=timedelta(days=2))
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (lat: 3, lon: 4)>
-array([[287.26828592, 288.89220492, 276.78387873, 288.2794351 ],
-       [279.63600649, 287.92811451, 286.82669327, 284.9137281 ],
-       [281.10191176, 281.22098533, 285.36948685, 286.56486298]])
-Coordinates:
-    time     datetime64[ns] 2018-01-05
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-

Using the rich display above, we can see that .sel indeed returned the data at the temporal value corresponding to the date 2018-01-05.

-
-
-

Interpolation

-

In this example, we are trying to extract a timeseries for Boulder, CO, which is located at 40°N latitude and 105°W longitude. Our DataArray does not contain a longitude data value of -105, so in order to retrieve this timeseries, we must interpolate between data points.

-

The .interp() method allows us to retrieve data from any latitude and longitude by means of interpolation. This method uses coordinate-value selection, similarly to .sel(). (For more information on the .interp() method, see the official documentation here.)

-
-
-
temp.interp(lon=-105, lat=40)
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 5)>
-array([278.40291893, 283.51242184, 284.0229578 , 281.48338375,
-       285.8550875 ])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
-    lon      int64 -105
-    lat      int64 40
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-
-

Info

-

In order to interpolate data using Xarray, the SciPy package must be imported. You can learn more about SciPy from the official documentation.

-
-
-
-
-

Slicing along coordinates

-

Frequently, it is useful to select a range, or slice, of data along one or more coordinates. In order to understand this process, you must first understand Python slice objects. If you are unfamiliar with slice objects, you should first read the official Python slice documentation. Once you are proficient using slice objects, you can create slices of data by passing slice objects to the .sel method, as shown below:

-
-
-
temp.sel(
-    time=slice('2018-01-01', '2018-01-03'), lon=slice(-110, -70), lat=slice(25, 45)
-)
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 3, lat: 2, lon: 2)>
-array([[[276.70400275, 281.04687463],
-        [275.22335944, 284.83358678]],
-
-       [[275.56234131, 275.07276144],
-        [283.03580664, 288.7763213 ]],
-
-       [[276.85323814, 281.81861453],
-        [283.2409484 , 284.50584781]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 2018-01-03
-  * lat      (lat) float64 25.0 40.0
-  * lon      (lon) float64 -100.0 -80.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-
-

Info

-

As detailed in the documentation page linked above, the slice function uses the argument order (start, stop[, step]), where step is optional.

-
-

Because we are now working with a slice of data, instead of our full dataset, the lengths of our coordinate axes have been shortened, as shown in the Jupyter rich display above. (You may need to use a different display technique if you are not running this page as a Jupyter Notebook.)

-
-
-

One more selection method: .loc

-

In addition to using the sel() method to select data from a DataArray, you can also use the .loc attribute. Every DataArray has a .loc attribute; in order to leverage this attribute to select data, you can specify a coordinate value in square brackets, as shown below:

-
-
-
temp.loc['2018-01-02']
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (lat: 3, lon: 4)>
-array([[287.33312259, 275.56234131, 275.07276144, 287.3768635 ],
-       [284.94226746, 283.03580664, 288.7763213 , 284.14675698],
-       [280.81123005, 291.60986448, 282.50638476, 273.47615803]])
-Coordinates:
-    time     datetime64[ns] 2018-01-02
-  * lat      (lat) float64 25.0 40.0 55.0
-  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-

This selection technique is similar to NumPy’s index-based selection, as shown below:

-
temp[1,:,:]
-
-
-

However, this technique also resembles the .sel() method’s fully label-based selection functionality. The advantages and disadvantages of using the .loc attribute are discussed in detail below.

-

This example illustrates a significant disadvantage of using the .loc attribute. Namely, we specify the values for each coordinate, but cannot specify the dimension names; therefore, the dimensions must be specified in the correct order, and this order must already be known:

-
-
-
temp.loc['2018-01-01':'2018-01-03', 25:45, -110:-70]
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 3, lat: 2, lon: 2)>
-array([[[276.70400275, 281.04687463],
-        [275.22335944, 284.83358678]],
-
-       [[275.56234131, 275.07276144],
-        [283.03580664, 288.7763213 ]],
-
-       [[276.85323814, 281.81861453],
-        [283.2409484 , 284.50584781]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 2018-01-03
-  * lat      (lat) float64 25.0 40.0
-  * lon      (lon) float64 -100.0 -80.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-

In contrast with the previous example, this example shows a useful advantage of using the .loc attribute. When using the .loc attribute, you can specify data slices using a syntax similar to NumPy in addition to, or instead of, using the slice function. Both of these slicing techniques are illustrated below:

-
-
-
temp.loc['2018-01-01':'2018-01-03', slice(25, 45), -110:-70]
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray (time: 3, lat: 2, lon: 2)>
-array([[[276.70400275, 281.04687463],
-        [275.22335944, 284.83358678]],
-
-       [[275.56234131, 275.07276144],
-        [283.03580664, 288.7763213 ]],
-
-       [[276.85323814, 281.81861453],
-        [283.2409484 , 284.50584781]]])
-Coordinates:
-  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 2018-01-03
-  * lat      (lat) float64 25.0 40.0
-  * lon      (lon) float64 -100.0 -80.0
-Attributes:
-    units:          kelvin
-    standard_name:  air_temperature
-
-

As described above, the arguments to .loc must be in the order of the DataArray’s dimensions. Attempting to slice data without ordering arguments properly can cause errors, as shown below:

-
-
-
# This will generate an error
-# temp.loc[-110:-70, 25:45,'2018-01-01':'2018-01-03']
-
-
-
-
-
-
-
-

Opening netCDF data

-

Xarray has close ties to the netCDF data format; as such, netCDF was chosen as the premier data file format for Xarray. Hence, Xarray can easily open netCDF datasets, provided they conform to certain limitations (for example, 1-dimensional coordinates).

-
-

Access netCDF data with xr.open_dataset

-
-

Info

-

The data file for this example, NARR_19930313_0000.nc, is retrieved from Project Pythia’s custom example data library. The DATASETS class imported at the top of this page contains a .fetch() method, which retrieves, downloads, and caches a Pythia example data file.

-
-
-
-
filepath = DATASETS.fetch('NARR_19930313_0000.nc')
-
-
-
-
-
Downloading file 'NARR_19930313_0000.nc' from 'https://github.com/ProjectPythia/pythia-datasets/raw/main/data/NARR_19930313_0000.nc' to '/home/runner/.cache/pythia-datasets'.
-
-
-
-
-

Once we have a valid path to a data file that Xarray knows how to read, we can open the data file and load it into Xarray; this is done by passing the path to Xarray’s open_dataset method, as shown below:

-
-
-
ds = xr.open_dataset(filepath)
-ds
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:                       (time1: 1, isobaric1: 29, y: 119, x: 268)
-Coordinates:
-  * time1                         (time1) datetime64[ns] 1993-03-13
-  * isobaric1                     (isobaric1) float32 100.0 125.0 ... 1e+03
-  * y                             (y) float32 -3.117e+03 -3.084e+03 ... 714.1
-  * x                             (x) float32 -3.324e+03 ... 5.343e+03
-Data variables:
-    u-component_of_wind_isobaric  (time1, isobaric1, y, x) float32 ...
-    LambertConformal_Projection   int32 ...
-    lat                           (y, x) float64 ...
-    lon                           (y, x) float64 ...
-    Geopotential_height_isobaric  (time1, isobaric1, y, x) float32 ...
-    v-component_of_wind_isobaric  (time1, isobaric1, y, x) float32 ...
-    Temperature_isobaric          (time1, isobaric1, y, x) float32 ...
-Attributes:
-    Originating_or_generating_Center:     US National Weather Service, Nation...
-    Originating_or_generating_Subcenter:  North American Regional Reanalysis ...
-    GRIB_table_version:                   0,131
-    Generating_process_or_model:          North American Regional Reanalysis ...
-    Conventions:                          CF-1.6
-    history:                              Read using CDM IOSP GribCollection v3
-    featureType:                          GRID
-    History:                              Translated to CF-1.0 Conventions by...
-    geospatial_lat_min:                   10.753308882144761
-    geospatial_lat_max:                   46.8308828962289
-    geospatial_lon_min:                   -153.88242040519995
-    geospatial_lon_max:                   -42.666108129242815
-
-
-
-

Subsetting the Dataset

-

Xarray’s open_dataset() method, shown in the previous example, returns a Dataset object, which must then be assigned to a variable; in this case, we call the variable ds. Once the netCDF dataset is loaded into an Xarray Dataset, we can pull individual DataArrays out of the Dataset, using the technique described earlier in this tutorial. In this example, we retrieve isobaric pressure data, as shown below:

-
-
-
ds.isobaric1
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'isobaric1' (isobaric1: 29)>
-array([ 100.,  125.,  150.,  175.,  200.,  225.,  250.,  275.,  300.,  350.,
-        400.,  450.,  500.,  550.,  600.,  650.,  700.,  725.,  750.,  775.,
-        800.,  825.,  850.,  875.,  900.,  925.,  950.,  975., 1000.],
-      dtype=float32)
-Coordinates:
-  * isobaric1  (isobaric1) float32 100.0 125.0 150.0 175.0 ... 950.0 975.0 1e+03
-Attributes:
-    units:                   hPa
-    long_name:               Isobaric surface
-    positive:                down
-    Grib_level_type:         100
-    _CoordinateAxisType:     Pressure
-    _CoordinateZisPositive:  down
-
-

(As described earlier in this tutorial, we can also use dictionary syntax to select specific DataArrays; in this case, we would write ds['isobaric1'].)

-

Many of the subsetting operations usable on DataArrays can also be used on Datasets. However, when used on Datasets, these operations are performed on every DataArray in the Dataset, as shown below:

-
-
-
ds_1000 = ds.sel(isobaric1=1000.0)
-ds_1000
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.Dataset>
-Dimensions:                       (time1: 1, y: 119, x: 268)
-Coordinates:
-  * time1                         (time1) datetime64[ns] 1993-03-13
-    isobaric1                     float32 1e+03
-  * y                             (y) float32 -3.117e+03 -3.084e+03 ... 714.1
-  * x                             (x) float32 -3.324e+03 ... 5.343e+03
-Data variables:
-    u-component_of_wind_isobaric  (time1, y, x) float32 ...
-    LambertConformal_Projection   int32 ...
-    lat                           (y, x) float64 ...
-    lon                           (y, x) float64 ...
-    Geopotential_height_isobaric  (time1, y, x) float32 ...
-    v-component_of_wind_isobaric  (time1, y, x) float32 ...
-    Temperature_isobaric          (time1, y, x) float32 ...
-Attributes:
-    Originating_or_generating_Center:     US National Weather Service, Nation...
-    Originating_or_generating_Subcenter:  North American Regional Reanalysis ...
-    GRIB_table_version:                   0,131
-    Generating_process_or_model:          North American Regional Reanalysis ...
-    Conventions:                          CF-1.6
-    history:                              Read using CDM IOSP GribCollection v3
-    featureType:                          GRID
-    History:                              Translated to CF-1.0 Conventions by...
-    geospatial_lat_min:                   10.753308882144761
-    geospatial_lat_max:                   46.8308828962289
-    geospatial_lon_min:                   -153.88242040519995
-    geospatial_lon_max:                   -42.666108129242815
-
-

As shown above, the subsetting operation performed on the Dataset returned a new Dataset. If only a single DataArray is needed from this new Dataset, it can be retrieved using the familiar dot notation:

-
-
-
ds_1000.Temperature_isobaric
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'Temperature_isobaric' (time1: 1, y: 119, x: 268)>
-[31892 values with dtype=float32]
-Coordinates:
-  * time1      (time1) datetime64[ns] 1993-03-13
-    isobaric1  float32 1e+03
-  * y          (y) float32 -3.117e+03 -3.084e+03 -3.052e+03 ... 681.6 714.1
-  * x          (x) float32 -3.324e+03 -3.292e+03 ... 5.311e+03 5.343e+03
-Attributes:
-    long_name:           Temperature @ Isobaric surface
-    units:               K
-    description:         Temperature
-    grid_mapping:        LambertConformal_Projection
-    Grib_Variable_Id:    VAR_7-15-131-11_L100
-    Grib1_Center:        7
-    Grib1_Subcenter:     15
-    Grib1_TableVersion:  131
-    Grib1_Parameter:     11
-    Grib1_Level_Type:    100
-    Grib1_Level_Desc:    Isobaric surface
-
-
-
-

Aggregation operations

-

As covered earlier in this tutorial, you can use named dimensions in an Xarray Dataset to manually slice and index data. However, these dimension names also serve an additional purpose: you can use them to specify dimensions to aggregate on. There are many different aggregation operations available; in this example, we focus on std (standard deviation).

-
-
-
u_winds = ds['u-component_of_wind_isobaric']
-u_winds.std(dim=['x', 'y'])
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'u-component_of_wind_isobaric' (time1: 1, isobaric1: 29)>
-array([[ 8.673963 , 10.212325 , 11.556413 , 12.254429 , 13.372146 ,
-        15.472462 , 16.091969 , 15.846294 , 15.195834 , 13.936979 ,
-        12.93888  , 12.060708 , 10.972139 ,  9.722328 ,  8.853286 ,
-         8.257241 ,  7.679721 ,  7.4516497,  7.2352104,  7.039894 ,
-         6.883371 ,  6.7821493,  6.7088237,  6.6865997,  6.7247376,
-         6.745023 ,  6.6859775,  6.5107226,  5.972262 ]], dtype=float32)
-Coordinates:
-  * time1      (time1) datetime64[ns] 1993-03-13
-  * isobaric1  (isobaric1) float32 100.0 125.0 150.0 175.0 ... 950.0 975.0 1e+03
-
-
-

Info

-

Recall from previous tutorials that aggregations in NumPy operate over axes specified by numeric values. However, with Xarray objects, aggregation dimensions are instead specified through a list passed to the dim keyword argument.

-
-

For this set of examples, we will be using the sample dataset defined above. The calculations performed in these examples compute the mean temperature profile, defined as temperature as a function of pressure, over Colorado. For the purposes of these examples, the bounds of Colorado are defined as follows:

-
    -
  • x: -182km to 424km

  • -
  • y: -1450km to -990km

  • -
-

This dataset uses a Lambert Conformal projection; therefore, the data values shown above are projected to specific latitude and longitude values. In this example, these latitude and longitude values are 37°N to 41°N and 102°W to 109°W. Using the original data values and the mean aggregation function as shown below yields the following mean temperature profile data:

-
-
-
temps = ds.Temperature_isobaric
-co_temps = temps.sel(x=slice(-182, 424), y=slice(-1450, -990))
-prof = co_temps.mean(dim=['x', 'y'])
-prof
-
-
-
-
-
- - - - - - - - - - - - - - -
<xarray.DataArray 'Temperature_isobaric' (time1: 1, isobaric1: 29)>
-array([[215.078  , 215.76935, 217.243  , 217.82663, 215.83487, 216.10933,
-        219.99902, 224.66118, 228.80576, 234.88701, 238.78503, 242.66309,
-        246.44807, 249.26636, 250.84995, 253.37354, 257.0429 , 259.08398,
-        260.97955, 262.98364, 264.82138, 266.5198 , 268.22467, 269.7471 ,
-        271.18216, 272.66815, 274.13037, 275.54718, 276.97675]],
-      dtype=float32)
-Coordinates:
-  * time1      (time1) datetime64[ns] 1993-03-13
-  * isobaric1  (isobaric1) float32 100.0 125.0 150.0 175.0 ... 950.0 975.0 1e+03
-
-
-
-
-

Plotting with Xarray

-

As demonstrated earlier in this tutorial, there are many benefits to storing data as Xarray DataArrays and Datasets. In this section, we will cover another major benefit: Xarray greatly simplifies plotting of data stored as DataArrays and Datasets. One advantage of this is that many common plot elements, such as axis labels, are automatically generated and optimized for the data being plotted. The next set of examples demonstrates this and provides a general overview of plotting with Xarray.

-
-

Simple visualization with .plot()

-

Similarly to Pandas, Xarray includes a built-in plotting interface, which makes use of Matplotlib behind the scenes. In order to use this interface, you can call the .plot() method, which is included in every DataArray.

-

In this example, we show how to create a basic plot from a DataArray. In this case, we are using the prof DataArray defined above, which contains a Colorado mean temperature profile.

-
-
-
prof.plot()
-
-
-
-
-
[<matplotlib.lines.Line2D at 0x7fa9b8dfb850>]
-
-
-../../_images/5eb9e51893b4e3d241b23b56ffc5f0e0787c3e7731d8dad7c5c73674fa16bd9f.png -
-
-

In the figure shown above, Xarray has generated a line plot, which uses the mean temperature profile and the 'isobaric' coordinate variable as axes. In addition, the axis labels and unit information have been read automatically from the DataArray’s metadata.

-
-
-

Customizing the plot

-

As mentioned above, the .plot() method of Xarray DataArrays uses Matplotlib behind the scenes. Therefore, knowledge of Matplotlib can help you more easily customize plots generated by Xarray.

-

In this example, we need to customize the air temperature profile plot created above. There are two changes that need to be made:

-
    -
  • swap the axes, so that the Y (vertical) axis corresponds to isobaric levels

  • -
  • invert the Y axis to match the model of air pressure decreasing at higher altitudes

  • -
-

We can make these changes by adding certain keyword arguments when calling .plot(), as shown below:

-
-
-
prof.plot(y="isobaric1", yincrease=False)
-
-
-
-
-
[<matplotlib.lines.Line2D at 0x7fa9b8ce6c50>]
-
-
-../../_images/28a10a4752ff52409d548e5325b9cd9ec1a653d170502898e66cf94b422de42f.png -
-
-
-
-

Plotting 2-D data

-

In the previous example, we used .plot() to generate a plot from 1-D data, and the result was a line plot. In this section, we illustrate plotting of 2-D data.

-

In this example, we illustrate basic plotting of a 2-D array:

-
-
-
temps.sel(isobaric1=1000).plot()
-
-
-
-
-
<matplotlib.collections.QuadMesh at 0x7fa9b8d6bdf0>
-
-
-../../_images/95ceb8b6dfa016b7190af29f5e5d3342eb8be7f1754bfaef4b214803c5beff69.png -
-
-

The figure above is generated by Matplotlib’s pcolormesh method, which was automatically called by Xarray’s plot method. This occurred because Xarray recognized that the DataArray object calling the plot method contained two distinct coordinate variables.

-

The plot generated by the above example is a map of air temperatures over North America, on the 1000 hPa isobaric surface. If a different map projection or added geographic features are needed on this plot, the plot can easily be modified using Cartopy.

-
-
-
-
-

Summary

-

Xarray expands on Pandas’ labeled-data functionality, bringing the usefulness of labeled data operations to N-dimensional data. As such, it has become a central workhorse in the geoscience community for the analysis of gridded datasets. Xarray allows us to open self-describing NetCDF files and make full use of the coordinate axes, labels, units, and other metadata. By making use of labeled coordinates, our code is often easier to write, easier to read, and more robust.

-
-

What’s next?

-

Additional notebooks to appear in this section will describe the following topics in greater detail:

-
    -
  • performing arithmetic and broadcasting operations with Xarray data structures

  • -
  • using “group by” operations

  • -
  • remote data access with OPeNDAP

  • -
  • more advanced visualization, including map integration with Cartopy

  • -
-
-
-
-

Resources and references

-

This tutorial contains content adapted from the material in Unidata’s Python Training.

-

Most basic questions and issues with Xarray can be resolved with help from the material in the Xarray documentation. Some of the most popular sections of this documentation are listed below:

- -

Another resource you may find useful is this Xarray Tutorial collection, created from content hosted on GitHub.

-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/conda.html b/_preview/419/foundations/conda.html deleted file mode 100644 index 1afaf4fa0..000000000 --- a/_preview/419/foundations/conda.html +++ /dev/null @@ -1,1038 +0,0 @@ - - - - - - - - Installing and Managing Python with Conda — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Installing and Managing Python with Conda

-
-
-

Overview

-

Conda is an open-source, cross-platform, language-agnostic package manager and environment management system that allows you to quickly install, run, and update packages within your work environment(s).

-

Here we will cover:

-
    -
  1. What are packages?

  2. -
  3. Installing Conda

  4. -
  5. Creating a Conda environment

  6. -
  7. Useful Conda commands

  8. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Installing and Running Python

Helpful

-
    -
  • Time to learn: 20 minutes

  • -
-
-
-
-

What are Packages?

-

A Python package is a collection of modules, which, in turn, are essentially Python scripts that contain published functionality. There are Python packages for data input, data analysis, data visualization, etc. Each package offers a unique toolset and may have its own unique syntax rules.

-

Package management is useful because you may want to update a package for one of your projects, but keep it at the same version in other projects to ensure that they continue to run as expected.

-
-
-

Installing Conda

-

We recommend you install Miniconda. You can do that by following the instructions for your machine.

-

Miniconda only comes with the conda package management system; it is a pared-down version of the full Anaconda Python distribution.

-

Installing Anaconda takes longer and uses up more disk space, but provides you with more functionality, including Spyder (a Python-specific integrated development environment or IDE) and Jupyter, in addition to other immediately installed packages. Also, the interface of Anaconda is great if you are uncomfortable with the terminal.

-

We recommend Miniconda for two reasons:

-
    -
  1. It’s quicker and takes up less disk space.

  2. -
  3. It encourages you to install only the packages you need in reproducible isolated environments for specific projects. This is generally a more robust way to work with open source tools.

  4. -
-

Once you have conda via the Miniconda installer, the next step is to create an environment and install packages.

-
-
-

Creating a Conda Environment

-

A Conda environment is an interoperable collection of specific versions of packages or libraries that you install and use for a specific workflow. The Conda package manager takes care of dependencies, so everything works together in a predictable way. One huge advantage of using environments is that any changes you make to one environment will not affect your other environments at all, so you are much less likely to “break” something!

-

To create a new Conda environment, type conda create --name and the name of your environment in your terminal, and then specify any packages that you would like to have installed. For example, to install a Jupyter-ready environment called sample_environment, type

-
conda create --name sample_environment python jupyterlab
-
-
-

Once the environment is created, you need to activate it in the current terminal session (see below).

-

It is a good idea to create a new environment for every project. Because Python is open source, new versions of the tools are released frequently. Isolated environments help guarantee that your scripts use the same versions of packages and libraries to ensure they run as expected. Similarly, it is best practice to NOT modify your base environment.

-
-
-

Useful Conda commands

-

Some other Conda commands that you will find useful include:

-
    -
  • Activating a specific environment

  • -
-
conda activate sample_environment
-
-
-
    -
  • Deactivating the current environment

  • -
-
conda deactivate
-
-
-
    -
  • Checking what packages/versions are installed in the current environment

  • -
-
conda list
-
-
-
    -
  • Installing a new package into the current environment

  • -
-
conda install somepackage
-
-
-
    -
  • Installing a specific version of a package into the current environment

  • -
-
conda install somepackage=0.17
-
-
-
    -
  • Updating all packages in the current environment to the latest versions

  • -
-
conda update --all
-
-
-
    -
  • Checking what conda environments you have

  • -
-
conda env list
-
-
-
    -
  • Deleting an environment

  • -
-
conda env remove --name sample_environment
-
-
-

You can find lots more information in the Conda documentation or this handy Conda cheat sheet.

-

If you’re not a command line user, the Anaconda navigator offers GUI functionality for selecting environments and installing packages.

-
-
-
-

Summary

-

Conda is a package and environment management system that allows you to quickly install, run, and update packages within your work environment(s). This is important for gathering all of the tools necessary for your workflow. Conda can be managed in the command line or in the Anaconda GUI.

- -
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/getting-started-github.html b/_preview/419/foundations/getting-started-github.html deleted file mode 100644 index 9969b45a5..000000000 --- a/_preview/419/foundations/getting-started-github.html +++ /dev/null @@ -1,865 +0,0 @@ - - - - - - - - Getting Started with GitHub — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- - GitHub Logo -
-

Getting Started with GitHub

-

Python and Jupyter are cool technologies, but they only scratch the surface of why you might want to adopt Python for your geoscience workflow.

-

This section will introduce GitHub, the de facto standard platform for collaboration and version control used by the open-source Python community.

-

We will walk users through these topics:

- -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/getting-started-jupyter.html b/_preview/419/foundations/getting-started-jupyter.html deleted file mode 100644 index a0eb4754d..000000000 --- a/_preview/419/foundations/getting-started-jupyter.html +++ /dev/null @@ -1,1028 +0,0 @@ - - - - - - - - Getting Started with Jupyter — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -

https://jupyter.org/assets/homepage/main-logo.svg

-
-

Getting Started with Jupyter

-
-
-

Overview

-

Project Jupyter is a project and community whose goal is to “develop open-source software, open-standards, and services for interactive computing across dozens of programming languages”. Jupyter consists of four main components: Jupyter Notebooks, Jupyter Kernels, Jupyter Lab, and Jupyter Hub. Jupyter can be executed locally and remotely.

-
    -
  1. Jupyter Notebooks

  2. -
  3. Jupyter Kernels

  4. -
  5. Jupyter Lab

  6. -
  7. Jupyter Hub

  8. -
  9. Executing Jupyter

  10. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Installing and Running Python: Python in Jupyter

Helpful

-
    -
  • Time to learn: 10 minutes

  • -
-
-
-
-

Jupyter Notebooks

-

The Jupyter Notebook software is an open-source web application that allows you to create and share Jupyter Notebooks (*.ipynb files). Jupyter Notebooks contain executable code, LaTeX equations, visualizations (e.g., plots, pictures), and narrative text. The code does not have to just be Python, other languages such as Julia or R are supported as well.

-

Jupyter Notebooks are celebrated for their interactive output that allows movement between code, code output, explanations, and more code - similar to how scientists think and solve problems. Jupyter Notebooks can be thought of as a living, runnable publication and make for a great presentation platform.

-
-
-

Jupyter Kernels

-

Software engines and their environments (e.g., conda environments) that execute the code contained in Jupyter Notebooks.

-
-
-

Jupyter Lab

-

A popular web application on which users can create and write their Jupyter Notebooks, as well as explore data, install software, etc.

-

You can find more information on running Jupyter Lab here.

-
-
-

Jupyter Hub

-

A web-based platform that authenticates users and launches Jupyter Lab applications for users on remote systems.

-
-
-

Executing Jupyter

-
-

Local Execution Model

-

You can launch JupyterLab from a terminal; it will open up in a web browser. The application will then be running in that web browser. When you open a notebook, Jupyter opens a kernel which can be tied to a specific coding language.

-

To launch the JupyterLab interface in your browser, follow the instructions in Installing and Running Python: Python in Jupyter.

-

Local Execution Model

-
-
-

Remote Execution Model

-

In the remote execution model, you start out in the browser, then navigate to a specific URL that points to a JupyterHub. On JupyterHub, you authenticate on the remote system, and then JupyterLab is launched and redirected back to your browser. The interface appears the same as if you were running Jupyter locally.

-

Remote Execution Model

-
-
-
-
-

Summary

-

Jupyter consists of four main components:

-
    -
  • Jupyter Notebooks (the “*.ipynb” files),

  • -
  • Jupyter Kernels (the work environment),

  • -
  • Jupyter Lab (a popular web application and interface for local execution),

  • -
  • and Jupyter Hub (an application and launcher for remote execution).

  • -
-
-

What’s next?

- -
-
- -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/getting-started-python.html b/_preview/419/foundations/getting-started-python.html deleted file mode 100644 index d84a5e040..000000000 --- a/_preview/419/foundations/getting-started-python.html +++ /dev/null @@ -1,870 +0,0 @@ - - - - - - - - Getting Started with Python — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
- On this page -
- -
-
-
-
-
- -
- -
-

Getting Started with Python

-

New Python users, start here!

-
-

Topics

- -
-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/basic-git.html b/_preview/419/foundations/github/basic-git.html deleted file mode 100644 index bd89b21fd..000000000 --- a/_preview/419/foundations/github/basic-git.html +++ /dev/null @@ -1,1435 +0,0 @@ - - - - - - - - Basic Version Control with git — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - Git Logo -
-

Basic Version Control with git

-
-

Overview:

-
    -
  1. The need for version control

  2. -
  3. Basic git usage

  4. -
  5. Making your first git commit

  6. -
  7. Viewing and comparing across the commit history

  8. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub?

Necessary

GitHub user account required

GitHub Repositories

Necessary

Issues and Discussions

Recommended

Cloning and Forking a Repository

Recommended

Configuring your GitHub Account

Recommended

-
    -
  • Time to learn: 45 minutes

  • -
-
-
-
-

About version control and git

-
-

What is version control (and why should we care)?

-

Version Control refers generally to systems for managing changes to documents or files. Version control systems let us keep track of what changes were made to a file, when they were made, and by whom. If you’ve ever used “Tracked changes” on a Word document with multiple authors, then you’ve seen a form of version control in action (though NOT one that is well suited to working with computer code!).

-

The need for version control is particularly acute when working with computer code, where small changes to the text can have huge impacts on the results of running the code.

-

Do you have a directory somewhere on your machine right now with five different versions of a Python script like this?

-
analysis_script_OLD.py
-analysis_script.py
-analysis_script_09122021.py
-analysis_script_09122021_edit.py
-analysis_script_NEW.py
-
-
-

A Version Control System (VCS) like git will replace this mess with a well-ordered and labelled history of edits that you can freely browse through, and will greatly simplify collaborating with other people on writing new code.

-
-
-

What is git?

-
-

Git is not GitHub

-

That’s the first thing to understand. GitHub is a web-based platform for hosting code and collaborating with other people. On the other hand, git is a command-line Version Control System (VCS) that you can download and install. It runs on your local computer as well as under the hood on GitHub. You need to understand something about version control with git in order to use many of GitHub’s collaboration features.

-
-
-

A little history and nomenclature

-

Git has been around since the mid-2000s. It was originally written by Linus Torvalds specifically for use in development of the Linux kernel. Git is FOSS and comes pre-installed on many Linux and Mac OS systems.

-

There are many other VCSs out there. A few that you might encounter in scientific codebases include Subversion, Mercurial, and CVS. However, git is overwhelmingly the VCS of choice for open-source projects in the Scientific Python ecosystem these days (as well as among software developers more generally).

-

There is no universally agreed-upon meaning of the name “git”. From the git project’s own README file:

-
-

The name “git” was given by Linus Torvalds when he wrote the very first version. He described the tool as “the stupid content tracker” and the name as (depending on your mood):

-
    -
  • random three-letter combination that is pronounceable, and not actually used by any common UNIX command. The fact that it is a mispronunciation of “get” may or may not be relevant.

  • -
  • stupid. contemptible and despicable. simple. Take your pick from the dictionary of slang.

  • -
  • “global information tracker”: you’re in a good mood, and it actually works for you. Angels sing, and a light suddenly fills the room.

  • -
  • “goddamn idiotic truckload of sh*t”: when it breaks

  • -
-
-
-
-

Git is a distributed VCS

-

Aside from being free and widely deployed, an important distinguishing feature of git is that it is a distributed Version Control System. Essentially this means that every git directory on every computer is a complete independent repository with complete history.

-

When we cloned the github-sandbox repository back in the Cloning and Forking section, we not only copied the current repository files but also the entire revision history of the repo.

-

In this section we are going to explore basic git usage on our local computer. Nothing that we do here is going to affect other copies of the repositories stored elsewhere. So don’t worry about breaking anything!

-

Later, we will explore how to collaborate on code repositories using GitHub. But in keep in mind the basic idea that all git repos are equal and independent! You will have separate copies of repos stored on your local machine and in your GitHub organization.

-

Now that we are oriented, let’s dive into some basic git usage with the github-sandbox repository!

-
-
-
-
-

Inspect a git repository with git status

-

First, make sure you followed the steps in the Cloning a repository lesson to make a clone of the github-sandbox repo on your local computer. Navigate to wherever you saved your copy of the repo.

-

Now meet your new best friend:

-
git status
-
-
-

which will always give you information about the current git repo. Try it! You should see something like this:

-
On branch main
-Your branch is up to date with 'origin/main'.
-
-nothing to commit, working tree clean
-
-
-

Two really important things here:

-
    -
  1. The first line show you the current branch (here called main). We’ll cover branching in more detail in the next lesson, but basically each branch is a completely independent version with its own history. When we start making changes to files, we’ll have to pay attention to which branch we’re currently on.

  2. -
  3. The last line nothing to commit, working tree clean tells us that we haven’t made any changes to files.

  4. -
-

You’ll want to use

-
git status
-
-
-

frequently to keep track of things in your repos.

-
-
-

Make some changes

-

Version control is all about keeping track of changes made to files. So let’s make some changes!

-

You may have noticed that the file sample.txt in the github-sandbox repository contains a typo. Here we’re going to fix the error and save it locally.

-
-

Create a new feature branch

-

Before we start editing files, the first thing to do is to create a new branch where we can safely make any changes we want.

-
-

Tip

-

While there’s nothing stopping us from making changes directly to the main branch, it’s often best to avoid this! The reason is that it makes collaboration trickier. See the lesson on Pull Requests.

-
-

Let’s create and checkout a new branch in one line:

-
git checkout -b fix-typo
-
-
-

Now try your new best friend again:

-
git status
-
-
-

You should see something like this:

-
On branch fix-typo
-nothing to commit, working tree clean
-
-
-

This tells us that we have switched over to a new branch called fix-typo, but there are not (yet) any changes to the files in the repo.

-
-
-

Time to make some changes

-

Now do the following:

-
    -
  • Using your favorite text editor, open the file github-sandbox/sample.txt.

  • -
  • Replace the word Fxing with the much more satisfying Fixing.

  • -
  • Save the changes.

  • -
  • Revisit your new best friend git status. It should now show something like this:

  • -
-
On branch fix-typo
-Changes not staged for commit:
-  (use "git add <file>..." to update what will be committed)
-  (use "git restore <file>..." to discard changes in working directory)
-	modified:   sample.txt
-
-no changes added to commit (use "git add" and/or "git commit -a")
-
-
-

Here git is telling us that the file sample.txt does not match what’s in the repository.

-

Of course we know what changed in that file because we just finished editing it. But here’s a quick and easy way to see the changes:

-
git diff
-
-
-

which should show you something like this:

-
diff --git a/sample.txt b/sample.txt
-index 4bc074c..edc31c0 100644
---- a/sample.txt
-+++ b/sample.txt
-@@ -4,6 +4,6 @@ We can use it to demonstrate making pull requests or raising issues in a GitHub
-
- One good way to contribute to a project is to make additions and/or edits to documentation!
-
--Fxing something as simple as a typo is a great way to get started as a contributor!
-+Fixing something as simple as a typo is a great way to get started as a contributor!
-
- Or, consider adding some more content to this file.
-
-
-

We can see here that git diff finds the line(s) where our current file differs from what’s in the repo, along with a few lines before and after for context.

-

The next step is to add our changes to the “official” history of our repo. This is a two-step process (staging and committing).

-
-
-
-

Stage and commit our changes

-

The commit is the centerpiece of the git workflow. Each commit is a specific set of changes, additions, and/or deletions of files that gets added to the official history of the repository.

-
-

Staging

-

Before we make a commit, we must first stage our changes. Think of staging simply as “getting ready to commit”. The two-step process can help avoid accidentally committing something that wasn’t ready.

-

To stage our changes, we use git add like this:

-
git add sample.txt
-
-
-

and now our new best friend tells us

-
On branch fix-typo
-Changes to be committed:
-  (use "git restore --staged <file>..." to unstage)
-	modified:   sample.txt
-
-
-

Now we see that all-important line Changes to be committed, telling us the contents of our staging area.

-

If you made a mistake (e.g., staged the wrong file), you can always unstage using git restore as shown in the git status output. Nothing is permanent until we commit!

-

(And if you accidentally commit the wrong thing? Don’t worry, you can always “go back in time” to previous commits – see below!)

-
-
-

Committing

-

It’s time to make a commitment. We can now permanently add our edit to the history of our fix-typo branch by doing this:

-
git commit -m 'Fix the typo'
-
-
-
-

Note

-

Every commit should have a “message” that explains briefly what the commit is for. Here we set the commit message with the -m flag and chose some descriptive text. Note, it’s critical to have those quotes around 'Fix the typo'. Otherwise the command shell will misinterpret what you are trying to do.

-
-

Now when we do git status we see

-
On branch fix-typo
-nothing to commit, working tree clean
-
-
-

And we’re back to a clean state! We have now added a new permanent change to the history of our repo (or more specifically, to this branch of the repo).

-
-
-
-

Going back in time

-

Each commit is essentially a snapshot in time of the state of the repo. So how can we look back on that history, or revert back to a previous version of a file?

-
-

Viewing the commit history with git log

-

A simple way to see this history of the current branch is this:

-
git log
-
-
-

You’ll see something like this:

-
commit 7dca0292467e4bbd73643556f83fd1c52b5c113c (HEAD -> fix-typo)
-Author: Brian Rose <brose@albany.edu>
-Date:   Mon Jan 17 11:31:49 2022 -0500
-
-    Fix the typo
-
-commit 35fcbd991f911e170df550db58f74a082ba18b50 (origin/main, origin/HEAD, main)
-Author: Kevin Tyle <ktyle@albany.edu>
-Date:   Thu Jan 13 11:29:40 2022 -0500
-
-    Close docstring quote on sample.py
-
-commit e56ea58071f150ec00904a50341a672456cbcb8f
-Author: Kevin Tyle <ktyle@albany.edu>
-Date:   Tue Jan 11 14:15:31 2022 -0500
-
-    Create sample.md
-
-commit f98d05e312d19a84b74c45402a2904ab94d86e45
-Author: Kevin Tyle <ktyle@albany.edu>
-Date:   Tue Jan 11 13:58:09 2022 -0500
-
-    Create sample.py
-
-
-

which shows the last few commits on this branch, including the commit number, author, timestamp, and commit message. You can page down to see the rest of the history -or just press Q to exit git log!

-
-

Note

-

Every commit has a unique hexadecimal checksum code like 7dca0292467e4bbd73643556f83fd1c52b5c113c. Your history will look a little different from the above!

-
-
-
-

Checking out a previous commit

-

Let’s say you want to retrieve the file sample.txt from the previous commit. Two possible reasons why:

-
    -
  1. You just want to take a quick look at something in the previous commit, but then go back to the current version. That’s what we’ll do here.

  2. -
  3. Maybe you don’t like the most recent commit and want to do some new edits starting from the previous commit – in effect, undoing the most recent commit and going back in time. The simplest way to do this is to create a new branch starting from the previous commit. We’ll cover branches more fully in the next lesson.

  4. -
-

To retrieve the previous commit, just use git checkout and the unique number code which you can just copy and paste from the git log output:

-
git checkout 35fcbd991f911e170df550db58f74a082ba18b50
-
-
-

You may see output that looks like this:

-
Note: switching to '35fcbd991f911e170df550db58f74a082ba18b50'.
-
-You are in 'detached HEAD' state. You can look around, make experimental
-changes and commit them, and you can discard any commits you make in this
-state without impacting any branches by switching back to a branch.
-
-If you want to create a new branch to retain commits you create, you may
-do so (now or later) by using -c with the switch command. Example:
-
-  git switch -c <new-branch-name>
-
-Or undo this operation with:
-
-  git switch -
-
-Turn off this advice by setting config variable advice.detachedHead to false
-
-HEAD is now at 35fcbd9 Close docstring quote on sample.py
-
-
-

(the details may vary depending on what version of git you are running).

-

By detached HEAD, git is telling us that we are NOT on the most recent commit in this branch.

-

If you inspect sample.txt in your editor, you will see that the typo Fxing is back!

-

As the git message above is reminding us, it’s possible to create an entirely new branch with changes that we make from this point in the history using git switch -c. But for now, let’s just go back to the most recent commit on our fix-typo branch:

-
git checkout fix-typo
-
-
-
-
-
-

Comparing versions

-

We already saw one use of the git diff command to look at changes in a repo. By default git diff will compare the currently saved files against the most recent commit.

-

We can also use git diff to compare across commits within a branch, or between two different branches. Here are some examples.

-
-

Compare across commits

-

To compare across any commits in our history, we again use the unique commit checksum that we listed with git log:

-
git diff HEAD 35fcbd991f911e170df550db58f74a082ba18b50
-
-
-

gives

-
diff --git a/sample.txt b/sample.txt
-index edc31c0..4bc074c 100644
---- a/sample.txt
-+++ b/sample.txt
-@@ -4,6 +4,6 @@ We can use it to demonstrate making pull requests or raising issues in a GitHub
-
- One good way to contribute to a project is to make additions and/or edits to documentation!
-
--Fixing something as simple as a typo is a great way to get started as a contributor!
-+Fxing something as simple as a typo is a great way to get started as a contributor!
-
- Or, consider adding some more content to this file.
-
-
-
-

Note

-

Here we use HEAD as an alias for the most recent commit.

-
-
-
-

Compare across branches

-

Recall that, since we have done all our editing in a new branch, the main branch still has the typo!

-

We can see this with git diff using the .. notation to compare two branches:

-
git diff main..fix-typo
-
-
-

The output is very similar:

-
diff --git a/sample.txt b/sample.txt
-index 4bc074c..edc31c0 100644
---- a/sample.txt
-+++ b/sample.txt
-@@ -4,6 +4,6 @@ We can use it to demonstrate making pull requests or raising issues in a GitHub
-
- One good way to contribute to a project is to make additions and/or edits to documentation!
-
--Fxing something as simple as a typo is a great way to get started as a contributor!
-+Fixing something as simple as a typo is a great way to get started as a contributor!
-
- Or, consider adding some more content to this file.
-
-
-

The git diff command is a powerful comparison tool (and maybe your second new best friend). For many more detail on its usage, see the git documentation.

-
-
-
-

Git commands mini-reference

-
-

Commands we used in this tutorial

-
    -
  • git status: see what branch we’re on and what state our repo is in.

  • -
  • git checkout: switch between branches (use the -b flag to create a new branch and check it out)

  • -
  • git diff: compare files between current version and last commit (default), between two commits, or between two branches.

  • -
  • git add: stage a file for a commit.

  • -
  • git commit: create a new commit with the staged files.

  • -
  • git log: see the commit history of our branch.

  • -
-
-
-

Some other git commands you’ll want to know

-

We’ll cover many of these in subsequent sections.

-
    -
  • git branch: list all the branch in the repo

  • -
  • git mv and git rm: git-enhanced versions of the mv (move file) and rm (remove file) commands. These will automatically stage the changes in your current branch.

  • -
  • git merge: merge changes from one branch into another.

  • -
  • git push and git pull: export or input changes between your local branch and a remote repository (e.g. hosted on GitHub).

  • -
  • git init: create a brand-new repo in the current directory

  • -
-
-
-
-
-

Summary

-
    -
  • Version control is an important tool for working with code files (or anything that is saved as plain text).

  • -
  • git is the most common version control software in use today.

  • -
  • git status is your new best friend because it gives you a quick view into what’s going on in a git repository.

  • -
  • Every branch of a git repository has a history which is a series of numbered and labelled commits.

  • -
  • You can view this history with git log

  • -
  • Making a new commit is a two-step process with git add and git commit.

  • -
  • Commits are non-destructive, meaning you can always go back in time to previous commits.

  • -
-
-

What’s Next?

-

Next we’ll explore the concept of branching in git repositories in more detail, including how to merge changes made on one branch into another branch.

-
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/contribute-to-pythia.html b/_preview/419/foundations/github/contribute-to-pythia.html deleted file mode 100644 index 5846ecb7d..000000000 --- a/_preview/419/foundations/github/contribute-to-pythia.html +++ /dev/null @@ -1,998 +0,0 @@ - - - - - - - - Contribute to Project Pythia via GitHub — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- - GitHub Logo -
-

Contribute to Project Pythia via GitHub

-
-

Overview:

-
    -
  1. Suggest a change

  2. -
  3. Make the edits

  4. -
  5. Create a Pull Request

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub

Necessary

GitHub Repositories

Necessary

Cloning and Forking

Necessary

Basic Version Control with git

Necessary

Issues and Discussions

Recommended

Branches

Necessary

Pull Requests

Necessary

Reviewing Pull Requests

Recommended

GitHub Workflows

Necessary

-
    -
  • Time to learn: 30 minutes

  • -
-
-

Now that you have become more familiar with how to use Git and GitHub, you might have an idea or some material that you want to contribute to Project Pythia! The Project Pythia Contributor’s Guide describes the steps required to submit a PR to any of Project Pythia’s repos. Here, we will go through an example of submitting a PR to pythia-foundations.

-
-
-

Suggest a change

-

One simple way to contribute is to fix a typo or suggest a change to one of the tutorials. For example, in the Computations and Masks with Xarray tutorial, let’s suggest a clarification that the sea surface temperature is called tos in the dataset we are using.

-Computations and Masks with Xarray -

We could open an issue to suggest this change in order to get feedback on the idea before we take the time to edit files, but since this is such a small change, let’s just create a PR.

-
-
-

Make the edits

-

We will follow the Forking Workflow described in the previous section of this tutorial, assuming pythia-foundations has already been forked:

-
    -
  • Create a new branch with a descriptive name

  • -
  • Make the changes and commit them locally

  • -
  • Push to the remote repository

  • -
  • Open a PR on GitHub

  • -
-

First, making the new branch,

-
git branch clarify-sst-tos
-git checkout clarify-sst-tos
-
-
-

There are a variety of ways to make changes, depending on the type of file, as well as preference. Here we want to edit a Jupyter Notebook (file extension .ipynb), so we can use JupyterLab. We find the file of interest at /core/xarray/computation-masking.ipynb and add in some text:

-Notebook in JupyterLab -

After saving and exiting (and checking for changes with a git status), we commit with the following:

-
git add core/xarray/computation-masking.ipynb
-git commit -m 'Mention that SST is called tos in the model'
-
-
-

Then pushing to our remote aliased origin:

-
git push origin clarify-sst-tos
-
-
-
-
-

Create a Pull Request

-

Now, going to our remote repo on GitHub, forked from pythia-foundations, we see that recent changes have been made. By clicking on the “Compare & pull request” button, we can open a PR, proposing that our changes be merged into the main branch of ProjectPythia/pythia-foundations.

-GitHub Forked Repo -

Project Pythia has an automated reviewer system: when a PR is created, two members of the organization will be randomly chosen to review it. If your PR is not immediately ready to be approved and merged, open it as a draft to delay the review process. As shown in this Git Branches section, the “Draft pull request” button is found using the arrow on the “Create pull request” button.

-

Let’s add the content tag and open this one as a draft for now:

-GitHub PR Creation -

For any PR opened in pythia-foundations, there will be a few checks that need to pass before merging is allowed. Once the deploy-book / build check has completed (which will likely take a few minutes), there will be a Deployment Preview URL commented by the github-actions bot that will take you to a build of the Pythia Foundations book with your edits. There you can ensure your edits show up as expected.

-GitHub Checks -

Once it is ready, click “Ready for review” to take it out of draft mode. Now we wait for any comments or reviews!

-
-
-
-

Summary

-
    -
  • You can contribute to Project Pythia by suggesting edits or adding content with a Pull Request

  • -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/git-branches.html b/_preview/419/foundations/github/git-branches.html deleted file mode 100644 index b39da0ce8..000000000 --- a/_preview/419/foundations/github/git-branches.html +++ /dev/null @@ -1,1177 +0,0 @@ - - - - - - - - Git Branches — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - Git Logo -
-

Git Branches

-

Git “branches” are an important component of many Git and GitHub workflows. If you plan to use GitHub to manage your own resources, or contribute to a GitHub hosted project, it is essential to have a basic understanding of what branches are and how to use them. For example, the best practices for a simple workflow for suggesting changes to a GitHub repository are: create your own fork of the repository, make a branch from your fork where your changes are made, and then suggest these changes move to the upstream repository with a Pull Request. This section of the GitHub chapter assumes you have read the prior GitHub sections, are at least somewhat familiar with git commands and the vocabulary (“cloning,” “forking,” “merging,” “Pull Request” etc), and that you have already created your own fork of the GitHub Sandbox Repository hosted by Project Pythia.

-
-

Overview:

-
    -
  1. What are Git Branches

  2. -
  3. Creating a New Branch

  4. -
  5. Switching Branches

  6. -
  7. Setting up a Remote Branch

  8. -
  9. Merging Branches

  10. -
  11. Deleting Branches

  12. -
  13. Updating Your Branches

  14. -
  15. Complete Workflow

  16. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub?

Necessary

GitHub user account required

GitHub Repositories

Necessary

Issues and Discussions

Recommended

Cloning and Forking a Repository

Necessary

Configuring your GitHub Account

Recommended

Basic Version Control with git

Necessary

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

What are Git branches?

-

Git branches allow for non-linear or differing revision histories of a repository. At a point in time, you can split your repository into multiple development paths (branches) where you can make different commits in each, typically with the ultimate intention of merging these branches and development changes together at a later time.

-

Branching is one of git’s methods for helping with collaborative document editing, much like “change tracking” in Google Docs or Microsoft Word. It enables multiple people to edit copies of the same document content, while reducing or managing edit collisions, and with the ultimate aim of merging everyone’s changes together later. It also allows the same person to edit multiple copies of the same document, but with different intentions. Some reasons for wanting to split your repository into multiple paths (i.e. branches) is to experiment with different methods of solving a problem (before deciding which method will ultimately be merged) and to work on different problems within the same codebase (without confusing which code changes are relevant to which problem).

-

These branches can live on your computer (local) or on GitHub (remote). They are brought together through Git pushes, pulls, merges, and Pull Requests. Pushing is how you transfer changes from your local repository to a remote repository. Pulling is how you fetch upstream changes into your branch. Merging is how you piece the forked history back together again (i.e. join two branches). And Pull Requests are how you suggest the changes you’ve made on your branch to the upstream codebase.

-
-

Pull Requests

-

We will cover Pull Requests more in-depthly in the next section.

-
-

One rule of thumb is for each development feature to have its own development branch until that feature is ready to be added to the upstream (remote) codebase. This allows you to compartmentalize your Pull Requests so that smaller working changes can be merged upstream independently of one another. For example, you might have a complete or near-complete feature on its own branch with an open Pull Request awaiting review. While you wait for feedback from the team before merging it, you can still work on a second feature on a second branch without affecting your first feature’s Pull Request. We encourage you to always do your work in a designated branch.

-
-
-

Creating a New Branch

-
-

Have you forked the repository?

-

Having forked (NOT just cloned) the GitHub Sandbox Repository is essential for following the steps in this book chapter. See the chapter on GitHub Cloning and Forking.

-
-

branching -The above flowchart demonstrates forking a remote repository, labeled “Upstream”, creating a local copy, labeled “Clone”, creating a new branch, “branchA”, and adding two commits, C3 and C4, to “branchA” of the local clone of the forked repository. Different commits can be added to different branches in any order without depending on or knowing about each other.

-

From your terminal, navigate to your local clone of your Github-Sandbox Repository fork:

-
cd github-sandbox
-
-
-

Let’s begin by checking the status of our repository:

-
git status
-
-
-

Git Status

-

You will see that you are already on a branch called “main”. And that this branch is up-to-date with “origin/main” and has nothing to commit.

-
-

The Main Branch

-

Historically, the main branch was called the master branch. The name change was relatively recent, so all of your GitHub repositories may not reflect this yet. See instructions to change your branch name at Github’s Branch Renaming documentation.

-
-

Now check the status of your remote repository with

-
git remote -v
-
-
-

Git Remote

-

We are set up to pull (denoted as ‘fetch’ in the output above) and push from the same remote repository.

-

Next, check all of your exising Git branches with:

-
git branch -a
-
-
-

Git Branch

-

You will see one local branch (main) and your remote branch (remotes/origin/HEAD and remotes/origin/main, where HEAD points to main). HEAD is the pointer to the current branch reference, or in essence, a pointer to your last commit. More on this in a later section.

-

Now, before we make some sample changes to our codebase, let’s create a new branch where we’ll make these changes:

-
git branch branchA
-
-
-

Check that this branch was created with:

-
git branch
-
-
-

Git NewBranch

-

This will display the current and the new branch. You’ll notice that current or active branch, indicated by the “*” is still the main branch. Thus, any changes we make to the contents of our local repository will still be made on main. We will need to switch branches to work in the new branch, branchA.

-
-
-

Switching Branches

-

To switch branches use the command git checkout as in:

-
git checkout branchA
-
-
-

To check your current branch use git status:

-
git status
-
-
-

Git Checkout

-

Notice that git status doesn’t say anything about being up-to-date, as before. This is because this branch only exists locally, not in our upstream GitHub fork.

-
-
-

Setting up a Remote Branch

-

While your clone lives locally on your laptop, a remote branch exists on your GitHub server. You have to tell GitHub about your local branch before these changes are reflected remotely in your upstream fork.

-

pushing -The above flowchart demonstrates pushing two new local commits (C3 and C4) to the corresponding remote branch. Before the push, the changes from these commits exist ONLY locally and are not represented on your upstream GitHub repository. After the push, everything is up-to-date.

-

Before we push this branch upstream, let’s make some sample changes (like C3 or C4) by creating a new empty file, with the ending “.py”.

-
touch hello.py
-
-
-

Git Status

-

You can check that this file has been created by comparing an ls before and after this command, and also with a git status that will show your new untracked file.

-

git add and git commit your new file and check the status again.

-

Git Add

-

Your new branch is now one commit ahead of your main branch. You can see this with a git log.

-

Git Log

-

In a real workflow, you would continue making edits and git commits on a branch until you are ready to push up to GitHub.

-

Try to do this with

-
git push
-
-
-

Git Push

-

You will get an error message, “fatal: The current branch branchA has no upstream branch.” So what is the proper method for getting our local branch changes up to GitHub?

-

First, we need to set an upstream branch to direct our local push to:

-
git push --set-upstream origin branchA
-
-
-

Thankfully, Git provided this command in the previous error message. If you cloned using HTTPS, you will be asked to enter your username and password, as described in GitHub’s PAT Creation page.

-

Set Upstream

-

We can see that this worked by doing a git branch -a

-

Notice the new branch called remotes/origin/newbranch. And when you do a git status you’ll see that we are up to date with this new remote branch.

-

Git Commit Status

-

On future commits you will not have to repeat these steps, as your remote branch will already be established. Simply push with git push to have your remote branch reflect your future local changes.

-
-
-

Merging Branches

-

Merging is how you bring your split branches of a repository back together again.

-

If you want to merge two local branches together, the steps are as follows:

-

Let’s assume your two branches are named branchA and branchB, and you want your changes from branchB to now be reflected in branchA

-
    -
  1. First checkout the branch you want to merge INTO:

  2. -
-
git checkout branchA
-
-
-
    -
  1. Then execute a merge:

  2. -
-
git merge branchB
-
-
-

If there were competing edits in the 2 branches that Git cannot automatically resolve, a merge conflict occurs. This typically happens if edits are to the same line in different commits. Conflicts can be resolved in the command line or in your GUI of choice (such as Visual Studio Code).

-

A Pull Request is essentially a merge that happens on an upstream remote. We will continue this demonstration and cover the specifics of merging via a Pull Request more thoroughly in the next section.

-

PR -The above flowchart demonstrates a simple Pull Request where the upstream main repository has accepted the changes from the feature branch of your fork. The latest commit to the Upstream Main repository is now C4. Your Feature branch can now be safely deleted.

-
-
-

Deleting Branches

-

After the feature you worked on has been completed and merged, you may want to delete your branch. -deletebranch

-

To do this locally, you must first switch back to main or any non-target branch. Then you can enter

-
git branch -d <branch>
-
-
-

for example

-
git branch -d branchA
-
-
-

To delete the branch remotely, type

-
git push <remote> --delete <branch>.
-
-
-

as in

-
git push origin --delete jukent/branchA
-
-
-
-
-

Updating Your Branches

-

Previously, we showed you how to merge branches together, combining the changes from two different branches into one. Afterwards you deleted your feature branch branchA. Your local clone and fork of your main branch have now both need to pull from the upstream repository.

-

pull -The above flowchart demonstrates pulling in the upstream changes from Upstream Main after a Pull Request has been merged, first into your fork and then into your clone. Before continuing to work, with new commits on the feature branch, it is best to pull in the upstream changes.

-

In this example, all of the changes to the branches were local and made by a single person, you. In a collaborative environment, other contributors may be making changes to their own feature branches (or main branch), which will ultimately be pushed up to the remote repository. Either way, your branches will become stale and need to be refreshed. The more time that passes by, the more likely this is to happen, particularly for an active GitHub repository. Here we show you how to sync your branches with the upstream branches.

-

Once a Pull Request has been merged, you will find that these upstream changes are not automatically included in your fork or your other branches. In order to include the changes from the upstream main branch, you will need to do a git pull.

-

First check if there are any upstream changes:

-
git status
-
-
-

Then, if there are no merge conflicts:

-
git pull
-
-
-

git pull is a combination of git fetch and git merge. That is it updates the remote tracking branches (git fetch) AND updates your current branch with any new commits on the remote tracking branch (git merge).

-

This same concept appplies to work in a team setting. Multiple authors will have their own feature branches that merge into the same Upstream Main repository via Pull Requests. It is important for each author to do regular git pulls to stay up to date with each other’s contributions.

-
-
-

Complete Workflow

-

All in all your Git Branching workflow should resemble this flow: -gitworkflow

-
    -
  1. Forking the upstream repository

  2. -
  3. Creating a local clone of your upstream fork

  4. -
  5. Creating a new branch

  6. -
  7. Switching branches

  8. -
  9. Making a commit

  10. -
  11. Setting up a remote branch

  12. -
  13. Merging branches via a PR

  14. -
  15. Deleting branches

  16. -
  17. Pulling from upstream

  18. -
-
-
-
-

Summary

-
    -
  • Git Branches allow you to independently work on different features of a project via differing revision histories of a repository.

  • -
  • A useful workflow is to create a new branch locally, switch to it and set up a remote branch. During your revision, push to your upstream branch and pull from main as often as necessary. Then suggest your edits via a Pull Request and, if desired, delete your branch after the merge.

  • -
- -
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-advanced.html b/_preview/419/foundations/github/github-advanced.html deleted file mode 100644 index febe09ffe..000000000 --- a/_preview/419/foundations/github/github-advanced.html +++ /dev/null @@ -1,925 +0,0 @@ - - - - - - - - Advanced GitHub Topics — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Advanced GitHub Topics

-
-

Note

-

This content is under construction!

-
-
-

Overview:

-
    -
  1. Overview 1

  2. -
  3. Overview 2

  4. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Prior GitHub Sections

Necessary

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

Content section

-
-
-
-

Summary

-
    -
  • Sum 1

  • -
  • Sum 2

  • -
-
-

What’s Next?

-

End of GitHub content

-
-
-
-

References

-
    -
  1. Ref 1

  2. -
  3. Ref 2

  4. -
-
-
- - - - -
- -
-
-
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-cloning-forking.html b/_preview/419/foundations/github/github-cloning-forking.html deleted file mode 100644 index f07325748..000000000 --- a/_preview/419/foundations/github/github-cloning-forking.html +++ /dev/null @@ -1,1113 +0,0 @@ - - - - - - - - Cloning and Forking a Repository — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- - GitHub Logo -
-

Cloning and Forking a Repository

-
-

Overview:

-
    -
  1. Cloning and forking a git repository

  2. -
  3. Cloning a repository

  4. -
  5. Forking a repository

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub?

Necessary

GitHub user account required

GitHub Repositories

Necessary

Issues and Discussions

Recommended

Command-line shell

Helpful

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

Cloning and forking

-

Cloning and forking are two related terms in the GitHub vernacular -that, unfortunately, are not always used consistently throughout -the web-o-sphere. In Project Pythia we use the term clone to refer to -making a local copy of a remote repository; the source for -the copy is a remote repo, and the destination for the copy is your -local laptop/desktop. When working with GitHub, a fork, on the -other hand, creates a copy of a GitHub repository on GitHub. In other -words, both the source and the destination of the fork operations are -hosted in the cloud on GitHub. Forking is performed via your GitHub -account. While the forked repository may be owned by anyone, the -newly created repository will be owned by you. Cloning, on the -other hand, is performed using a Git command. Naturally, since the -destination of the clone operation is your local computer, you will -own the cloned contents. In either case, whether you clone or fork, -any changes you make to the newly created repository will not impact -the original without taking explicit action (e.g. performing a -push or submitting a Pull Request, the topics of later sections -in this guide).

-

Cloning and forking are often used together (more on this later). -The illustration below demonstrates the operation of a Fork of a -remote repository (UPSTREAM), followed by a clone of the newly -created ORIGIN.

-

clone-and-fork

-
-
-

Cloning a repository

-

Cloning is ideal for the following scenarios:

-
    -
  1. You wish to download, build, and install the latest version of a software package.

  2. -
  3. You would like to experiment with a repository on your local computer, but do not desire to maintain a separate copy of it (termed a fork, to be covered later in this lesson) on your GitHub account.

  4. -
  5. You have previously forked a repository to your own GitHub account, and now wish to make changes to it for possible incorporation into the original repo, via a Pull Request.

  6. -
-

Let’s consider the 2nd scenario. Say you wish to copy a GitHub repository to a computer you have access to (which could be your own computer, or one you have access to at work or school).

-

We’ll use a very basic repo that is part of the Project Pythia organization as our example.

-

First, point your browser to https://github.com/ProjectPythia/github-sandbox:

-SandboxRepo -
-

We see that in the repository, there exists five files. Above the list of files is this row:

-RepoTools -
-

Click on the green Code button to the right:

-CodeClone -
-

Select the HTTPS option, and click on the copy-to-clipboard icon:

-CodeCloneHTTPS -
-
-

Tip

-

This link points to where the repository “lives” on GitHub. We will use the term origin to refer to this location.

-
-

Now, open up a terminal on your local computer, and if desired, cd into a directory that you’d like to house whatever repos you clone. Type git clone, and then paste in the URL that you copied from GitHub (i.e., the origin):

-
git clone https://github.com/ProjectPythia/github-sandbox.git
-
-
-

You’ll see something like the following:

-
Cloning into 'github-sandbox'...
-remote: Enumerating objects: 15, done.
-remote: Counting objects: 100% (15/15), done.
-remote: Compressing objects: 100% (14/14), done.
-remote: Total 15 (delta 3), reused 0 (delta 0), pack-reused 0
-Receiving objects: 100% (15/15), 7.41 KiB | 2.47 MiB/s, done.
-Resolving deltas: 100% (3/3), done.
-
-
-
-

Windows users

-

While git is typically part of a Linux or Mac OS command-line shell, similar functionality must be installed if you are running Windows. Download and install the Git for Windows package.

-
-

Now, you can cd into the github-sandbox directory which has been created and populated with the exact contents of the origin’s repository at the time you cloned it. If you have a Python installation, you could then type

-
python sample.py
-
-
-

to run the sample Python script. You should see the following output:

-
Hello, Python learners!
-
-
-

By virtue of cloning the repo, git automatically registers the URL of the origin’s repository on GitHub. You can show this by typing the following:

-
git remote -v
-
-
-

You should see:

-
origin  git@github.com:ProjectPythia/github-sandbox.git (fetch)
-origin  git@github.com:ProjectPythia/github-sandbox.git (push)
-
-
-
-

Tip

-

We discuss the git command-line interface in the Basic version control with git lesson.

-
-

Congratulations! You have now cloned a GitHub repository!

-

Now, let’s consider the 3rd scenario for cloning… which involves the related topic of forking.

-
-
-

Forking a repository

-

Forking is similar to cloning, but has a bit more involved workflow. Scenarios where forking a repo is indicated include the following:

-
    -
  1. You wish to collaborate on projects that are hosted on GitHub, but you are not one of that project’s maintainers (i.e., you do not have write permissions on it).

  2. -
  3. You wish to experiment with changing or adding new features to a project, and do not immediately intend to merge them into the original project’s repo (aka, the upstream repository).

  4. -
-

In a fork, you create a copy of an existing repository, but store it in your own personal GitHub organization (recall that when you create a GitHub account, the organization name is your GitHub user ID).

-

Let’s say we intend to make some changes to the Project Pythia Sandbox repo, that ultimately we’ll submit to the original repository as a Pull request.

-
-

Note

-

Be sure you have logged into GitHub at this time!

-
-

Notice at the top right of the screen, there is a Fork button:

-Fork -
-

Click on it:

-ForkDest -
-

You should see your GitHub user ID (if you administer any other GitHub organizations, you will see them as well). Click on your user ID to complete the fork. After a few seconds, your browser will be redirected to the forked repo, now residing in your personal GitHub organization:

-ForkPost -
-

Notice that the Fork button on the upper right has incremented by one, and there is also is a line relating your fork to the original repo:

-ForkBranch -
-
-

Tip

-

We discuss branches in the Git Branches lesson.

-
-

You now have a copy (essentially a clone) of the forked repository, which is now owned by you.

-

You could, at this point, select one of the files in the repository and use GitHub’s built-in editor to make changes to these text-based files. However, the typical use case that leverages the collaborative power of GitHub and its command-line cousin, git, involves cloning your forked copy of the repo to your local computer, where you can then perform your edits, and (in the case of software) test them on your system.

-

Cloning your fork is the same as cloning the original repo. Click on the Code button, select the HTTPS protocol, copy the URL to the clipboard, and then run git clone <URL> on your local computer. In this case, you will need to either run this command in a different directory, or rename the destination directory with git clone <URL> <directory-name>, since it will by default use the name of the repo, github-sandbox.

-
-

Tip

-

Unlike cloning, forking is not an option supported by the git command-line interface. In other words, git fork is not a valid command.

-
-

Once you’ve cloned the fork to your local machine, try running git remote -v again. You will see that the origin URL now points to your GitHub account or organization.

-

The main purpose of cloning and forking a remote repository is so that you can make changes to the contents of those repositories in a safe and version-controlled manner. The process of making changes and submitting them as Pull Requests to the original repository is covered in our lesson on Opening a Pull Request on GitHub, but the workflow is as follows:

-
    -
  1. Edit an existing file or files, and/or create new files.

  2. -
  3. Stage your changes by running git add.

  4. -
  5. Commit your changes by running git commit.

  6. -
  7. (If you created a fork): Push your changes to your fork by running git push.

  8. -
  9. (If you did not create a fork): Push your changes to the upstream repository by running git push. This assumes you have write permissions on the upstream repository.

  10. -
  11. In GitHub, create a Pull request.

  12. -
-
-
-
-

Summary

-
    -
  • The process of making a local copy of a GitHub repository is called cloning. The destination for the cloned copy is whatever machine you ran the git clone command from.

  • -
  • Forking a repository also makes a copy of a GitHub repo, but places it in your GitHub organization in the GitHub.com cloud.

  • -
  • Forking allows you to modify a remote repo, without affecting the original version.

  • -
  • After cloning your fork to your local computer, you can make changes to your copy, which you can then submit to the original repo as a Pull request.

  • -
-
-
-

Things to try

-
    -
  • Clone another GitHub-hosted repository that is of interest to you.

  • -
  • Try creating a fork of that repository.

  • -
-
-

What’s Next?

-

In the next lesson, you will set some configurations on your GitHub account that enable uploads (aka pushes) from your local computer to GitHub. You will also configure notifications on your GitHub account.

-
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-issues.html b/_preview/419/foundations/github/github-issues.html deleted file mode 100644 index fe1cbd510..000000000 --- a/_preview/419/foundations/github/github-issues.html +++ /dev/null @@ -1,996 +0,0 @@ - - - - - - - - Issues and Discussions — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- - GitHub Logo -
-

Issues and Discussions

-
-

Overview:

-
    -
  1. What are Issues and Discussions?

  2. -
  3. Examine an existing Issue

  4. -
  5. Examine an existing Discussion

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub?

Necessary

GitHub Repositories

Necessary

-
    -
  • Time to learn: 5 minutes

  • -
-
-
-
-

What are Issues and Discussions?

-

GitHub provides two different, but related mechanisms for communicating -within a repository about a project: Issues and Discussions. -Issues are more like “todo” items; they are task-focused. For example, Issues -are often used to report and track bugs, request new features, or -perhaps note a performance problem. Ultimately, the maintainers of -a project may resolve the issue by fixing the bug, adding the -feature, etc., and then closing the resolved issue, marking the -task as completed. GitHub Discussions, much like the name implies, -are more open ended, and may not have a resolution. Asking about a -topic, discussing the merits of a new feature, or even advertising -an event, such as a tutorial for your project, are all examples -of Discussions.

-

In the text below we discuss Issues in more detail, followed by -a discussion on Discussions. Keep in mind that when initiating a -conversation on GitHub, it is often unclear whether something is -more suited as an Issue or a Discussion. We, the creators of -Project Pythia, struggle with this ourselves. If you’re not sure, simply pick -one. Fortunately, the GitHub developers recognized this dilemma, and -made it easy to convert Issues into Discussions and vice versa.

-
-
-

Issues

-

To get started, let’s take a look at the Issues page in Project Pythia’s pythia-foundations repository:

-Pythia Issues -

By default, it shows all open Issues, but we can see all closed Issues by clicking “Closed”.

-Pythia Closed Issues -

Issues, Discussions, and Pull Requests are all numbered for easy reference. By opening, resolving, and then closing an issue, we are leaving behind a searchable public record of what the issue was, why we thought it was important, and how we resolved it. This is great for project management, since it gets old Issues out of the way without actually deleting them.

-

Let’s now examine Issue #144.

-Pythia Issue 144 -

As you can see, some broken links were found in one of the Pythia Foundations tutorials, likely because the site being linked recently had its structure changed. An additional comment was added, as well as a label to help filtering/sorting Issues by topic. We then see that this issue was mentioned (by typing the issue number) elsewhere in the repository. In this case, it was mentioned in Pull Request #145, which makes the changes to fix the issue. We can also see that the PR has been merged, which means the changes have been incorporated into the main branch of the code.

-

Like this example, Issues can notify others of bugs or typos, but they can also be used as “calls to action”, whether you plan on addressing the issue yourself, or are hoping that someone else will be interested in making the changes. Issues #97 and #98 are examples of this, in which ideas for changes are proposed and then addressed at a later time.

-

A new issue can be opened by pressing the “New issue” button on the top right of the Issues page. Depending on the repository, you may be prompted to choose from a template, or you may just see title and text boxes to fill out.

-
-
-

Discussions

-

Discussions, on the other hand, are more open-ended and do not necessarily suggest a change or addition to the repository. Here is the Discussions page for Pythia Foundations:

-Pythia Foundations Discussions -

Let’s take a look at Discussion #156.

-Pythia Discussion 156 -

This discussion brings up a resource relevant to the repository that could help others, but it is not suggesting a change like an issue would. Other Discussions might include announcements, Q&A, or general thoughts about the repository.

-

GitHub also makes it simple to reference a Discussion in an Issue (and vice versa), -which can help provide background and context for a piece of work.

-
-
-
-

Summary

-
    -
  • GitHub provides Issues and Discussions to facilitate collaboration.

  • -
  • Issues are specific and actionable, while Discussions are open-ended.

  • -
  • If you want to discuss a topic and you’re not sure if it is an Issue -or a Discussion, just pick one. It will be okay. :-)

  • -
-
-

What’s Next?

-

We will work through cloning and forking an example repository.

-
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-pull-request.html b/_preview/419/foundations/github/github-pull-request.html deleted file mode 100644 index 7a1174c8a..000000000 --- a/_preview/419/foundations/github/github-pull-request.html +++ /dev/null @@ -1,1083 +0,0 @@ - - - - - - - - Opening a Pull Request on GitHub — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
- -
-
-
- -
- - GitHub Logo -
-

Opening a Pull Request on GitHub

-

A Pull Request, aka a “merge request,” is an event that occurs when a project contributor begins the process of merging new code changes from a feature branch with the main project repository.

-
-

Overview:

-
    -
  1. What is a Pull Request?

  2. -
  3. Opening a Pull Request

  4. -
  5. Pull Request Features

  6. -
  7. GitHub Workflows

  8. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub

Necessary

GitHub Repositories

Necessary

Cloning and Forking

Necessary

Basic Version Control with git

Necessary

Issues and Discussions

Recommended

Branches

Necessary

-
    -
  • Time to learn: 60 minutes

  • -
-
-
-
-

What is a Pull Request?

-

A Pull Request (PR) is a formal mechanism for requesting that changes -that you have made to one repository are integrated (merged) into -another repository. Typically, the changes are reviewed by the -maintainers of the destination repository, potentially triggering -a cycle of revisions, before the PR is “merged”, and your changes -become part of the destination repo.

-

Just like Issues, PRs have -their own discussion forum for communicating about the proposed -changes. In fact, not only can maintainers or collaborators communicate -about your PR via GitHub, they can also suggest changes and may -even be able to make changes of their own by pushing follow-up -commits. All of the activity, from start to finish, is tracked -inside of the PR and can be reviewed at any time.

-

When a contributor to a project creates a PR they are requesting -that the owners of another destination repository pull a git -branch from the contributor’s repository and merge the contents of -the branch into a branch of the destination repository. This means -that the contributor must provide four pieces of information: the -contributor’s repository, the contributor’s branch, the destination -repository, and finally, the destination branch.

-

A typical sequence of steps consists of the following:

-
    -
  1. A contributor clones a personal remote repository, creating a local copy

  2. -
  3. The contributor creates a new branch in their local repository

  4. -
  5. The contributor makes changes to the branch and commits them to -their local repository

  6. -
  7. The contributor pushes the branch to a remote repository

  8. -
  9. The contributor submits a PR via GitHub

  10. -
-

After the maintainers or collaborators of the destination review -the changes, and any suggested revisions are made, the project -maintainer merges the feature into the destination repository and -closes the PR.

-
-
-

Opening a Pull Request

-

The demonstration is a continuation from the GitHub Branches chapter. Here, we will move from your local terminal to GitHub.

- -
-

Switch Branches

-

If you click on the branch main you’ll see the list of these branches.

-

GitHub Branches

-

There you can click on the branch branchA to switch branches.

-

New Branch

-

Here you will see the message, “This branch is 1 commit ahead of ProjectPythia:main.” Next to this message you’ll see either the option to “Contribute” (which opens a Pull Request) or “Fetch Upstream” (which pulls in changes from the original repository). And just above your files you’ll see your most recent commit.

-
-
-

Open a Draft Pull Request

-

Click on the “Open pull request” button under the “Contribute” drop-down.

-

Contribute

-

This will send you to a new page. Notice that you are now in “ProjectPythia/github-sandbox” and not your fork.

-

Compare

-

The page will have the two branches you are comparing with an arrow indicating which branch is to be merged into which. Here, base is the upstream origin and head is your forked repository. If you wanted, you could click on these branches to switch the merge configuration. Underneath that you’ll see a green message, “Able to merge. These branches can be automatically merged.” This message means that there are no conflicts. We will discuss conflicts in a later chapter.

-

In a one-commit PR, the PR title defaults to your commit message. You can change this if you’d like. There is also a space to add a commit message. This is your opportunity to explain your changes to the owners of the upstream repository.

-

Message

-

And if you scroll down, you’ll see a summary of this PR with every commit and changed file listed.

-

Summary

-

Click the arrow next to “Create Pull Request” to change this to a draft PR.

-

To Draft

-

Once you’ve clicked “Draft Pull Request,” you will be directed to the page of your new PR. Here you can add more comments or request reviews.

-

Draft PR

-
-
-
-

Pull Request Features

-

Now let’s look at the features and discussions in an open (draft) PR. -Clicking “Files Changed” allows you to see all of the changes that would be merged with this PR.

-

Files

-

If you are working in a repository that has automatic checks, it is a good idea to wait for these checks to pass successfully before you request reviewers or change to a non-draft PR. Do this by clicking “Ready for Review.”

-

Review

-

When working on a project with a larger team, do NOT merge your Pull Request before you have the approval of your teammates. Every team has their own requirements and best practice workflows, and will discuss/approve/reject Pull Requests together. We will cover more about the ways to interact with PRs through conversations and reviews in a later section.

-

To someone with write permissions on the repository, the ability to merge will look like this green button: -Green

-

However, this PR will NOT be merged, as the GitHub-Sandbox repository is intended to be static.

-
-
-

GitHub Workflows

-

The above demonstration is an example of the Git Forking Workflow, because we forked the GitHub Sandbox repository before making our feature branches. This is most common when you do NOT have write-access to the upstream repository.

-

This differs from the Feature Workflow, where all contributors work on a single, remote GitHub repository in specific feature branches. This is common when all contributors DO have write-access to the upstream repository.

-

The steps leading up to creating your PR depend on your workflow. The main difference in creating the PR is that -the contributor now, for the Feature Workflow, navigates to the upstream, remote -repository, not a personal remote fork, and initiates the PR there.

-

We will cover GitHub Workflows in greater detail in the next chapter.

-
-
-
-

Summary

-
    -
  • A Pull Request (PR) is a formal mechanism for requesting that changes -that you have made to one repository are integrated (merged) into -another repository.

  • -
  • The steps that lead up to -the PR depend your GitHub Workflow.

  • -
-
-

What’s Next?

-

In the next lesson we will learn more about Reviewing Pull Requests.

-
-
-
-

References

-
    -
  1. GitHub’s Collaborating with Pull Requests

  2. -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-repos.html b/_preview/419/foundations/github/github-repos.html deleted file mode 100644 index 8d4acfde1..000000000 --- a/_preview/419/foundations/github/github-repos.html +++ /dev/null @@ -1,1010 +0,0 @@ - - - - - - - - GitHub Repositories — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- - GitHub Logo -
-

GitHub Repositories

-
-

Overview:

-
    -
  1. Explore GitHub Repositories

  2. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub?

Necessary

-
    -
  • Time to learn: 15 minutes

  • -
-
-
-
-

What is a GitHub repository?

-

GitHub gives the following explanation of a repository:

-
-

A repository is usually used to organize a single project. Repositories can contain folders and files, images, videos, spreadsheets, and data sets – anything your project needs. Often, repositories include a README file, a file with information about your project. GitHub makes it easy to add one at the same time you create your new repository. It also offers other common options such as a license file.

-
-

In short, it is a collection of files. Each GitHub repository has an owner, which could be an individual or an organization. Repositories can also be set to public or private, determining who can see and interact with it. While a repository can simply store files, GitHub is designed with collaboration in mind. Three key collaborative tools in GitHub are:

-
    -
  1. Issues: report a bug, plan improvements, or provide feedback to others working on the repository.

  2. -
  3. Discussions: post ideas or other conversations that are not as specific or actionable as an Issue.

  4. -
  5. Pull requests: We will go into the specifics later, but a Pull request allows a user to propose a change to any of the files within a repository.

  6. -
-
-

Tip

-

Typically, a GitHub repository will always include the Issues and Pull requests tabs. Discussions are not enabled by default, but are increasingly prevalent.

-
-
-
-

What are some examples of repositories?

-

All of the Python packages covered (e.g. Numpy and Xarray) in this Foundations book have associated GitHub repositories, as well as Python itself:

-NumPy GitHub -Xarray GitHub -Python GitHub -

As you can see by the recent timestamps, these repositories are actively changing; this reflects the adaptability of the open-source software ecosystem surrounding Python.

-
-

Tip

-

Notice that each of the three Repositories each exist as part of their own Organization. In other words, the NumPy repository exists within the NumPy organization; the Xarray repo exists within the Pydata org, and so forth.

-

When you create your own GitHub account, your user ID functions as the organization. Any repositories you create (and therefore, own) will exist within that org.

-
-

Another example is this project’s Pythia Foundations repository, on which this tutorial is stored. It is owned by the Project Pythia organization. This organization also owns several other repositories that store the files needed to generate https://projectpythia.org/, among other things.

-
-
-

GitHub’s distributed repositories

-

Finally, we introduce an important concept that is vital to your -understanding when working with GitHub. It is the source of GitHub’s power, as well -as much of its complexity. GitHub repositories -are distributed; in the general case, there is more than one -repository for any project. In fact, repositories can come and go -at any time, created and deleted as need dictates. Creating new -repositories from existing ones, synchronizing them, and managing them -are the topics of later sections. For now, it is only important to -understand that for a GitHub-managed project, there is typically one -“official” repository, often called the “upstream” repository, and it lives on GitHub.com. There may be any -number of copies of the “official” repository, known as forks (or origins, -if it is owned by you), -that also reside on GitHub.com. Repos that are hosted on GitHub.com -are referred to as remotes. In addition to the remotes, there may -be one or more copies of the remotes on your desktop or laptop -computer that are referred to as locals. A conceptual diagram of -the various repos is shown in the image below.

-

GitHub repositories

-
-
-
-

Things to try:

-
    -
  1. Browse the NumPy, Xarray, Python, and Pythia Foundations repos.

  2. -
  3. Browse the organizations (e.g., Pydata) which house the repos within.

  4. -
  5. Check out GitHub’s “Create a repo” tutorial to learn how to create your own repository!

  6. -
-
-
-
-

Summary

-
    -
  • GitHub’s Repositories are collections of files.

  • -
  • Issues, Discussions, and Pull requests can be used to collaborate within a repository.

  • -
  • A GitHub Organization contains Repositories.

  • -
-
-

What’s Next?

-

We will further explore Issues and Discussions.

-
-
-
-

References

-
    -
  1. GitHub’s quickstart guide

  2. -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-setup-advanced.html b/_preview/419/foundations/github/github-setup-advanced.html deleted file mode 100644 index 4afeff75b..000000000 --- a/_preview/419/foundations/github/github-setup-advanced.html +++ /dev/null @@ -1,1057 +0,0 @@ - - - - - - - - Configuring Your GitHub Account — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - GitHub Logo -
-

Configuring Your GitHub Account

-
-

Overview:

-
    -
  1. Configure your GitHub account for secure logins via ssh and/or https

  2. -
  3. Set up notifications on repositories you own or follow

  4. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub?

Necessary

GitHub user account required

GitHub Repositories

Necessary

Issues and Discussions

Recommended

Cloning and Forking a Repository

Recommended

-
    -
  • Time to learn: 35 minutes

  • -
-
-
-
-

GitHub secure key generation

-

When you signed up for your free account on GitHub, you established a user ID and its corresponding password. Many of the repositories that GitHub serves are readable from anywhere, not even requiring a GitHub account.

-

However, especially when you use the git command-line interface to access a GitHub-hosted repo, there are cases when you need to provide an additional set of login credentials. Some of these cases are:

-
    -
  1. When you want to clone a private, as opposed to public GitHub repository (read-access)

  2. -
  3. When you wish to push to a repo (write-access)

  4. -
-

For these use-cases, you won’t be able to simply type your GitHub user ID and password from the command line. Instead, you need to set up access tokens that live in two places: in your GitHub account, and in your local computer’s file system.

-

GitHub supports two means of key-based access: via https, and via ssh.

-

For example, one can clone Project Pythia’s Sandbox repository using a URL for the https protocol:

-GitHub Clone https -
-

The URL in this case is https://github.com/ProjectPythia/github-sandbox.git

-

Similarly, if you click on the SSH tab:

-GitHub Clone ssh -
-

Here, the URL is git@github.com:ProjectPythia/github-sandbox.git

-
-
-

Generate a secure personal access token for https

-

First, you will create a secure token in your GitHub account settings, and then use the token on your local computer.

-

Follow the steps with helpful screenshots at GitHub’s PAT Creation page.

-
-

Tip:

-

If using the https protocol to push to a remote repo, you must have generated and downloaded a personal access token. You may also need it when cloning, if the remote repo is not open to all.

-
-
-
-

Generate an SSH public/private keypair

-

First, on your local computer, you will create an SSH public/private keypair, and then upload the public key to your GitHub account.

-

Follow the steps with helpful screenshots at GitHub’s Connecting to GitHub with SSH page.

-
-

Tip:

-

If using the ssh protocol to clone or push, you must have generated and created an ssh key-pair.

-
-
-
-

HTTPS vs SSH: Either is fine!

-

Either https or ssh works fine. Choose whatever you prefer. See this overview of the pros and cons of each protocol.

-
-
-
-
-

GitHub notifications

-

In keeping with the social network aspect of GitHub, you can follow particular repositories that are of interest to you. Additionally, once you begin contributing to a repository, you may wish to be notified when Pull Requests are made, Issues are posted, your code review is requested, and so on. While it’s easy to have GitHub email you at the address you used when you registered for your GitHub account, you may wish to avoid email clutter.

-
-

Email notifications

-

Let’s say you wish to monitor (or watch) the Project Pythia GitHub Sandbox repository and receive emails about it.

-

Click on the Watch link near the top of the page:

-GitHub Watch -
-

You can then select what type of notifications you wish to receive. For example, you may want to receive all notifications related to that repo:

-GitHub Watch All Activity -
-

You will then receive email at the address you used when you signed up for GitHub whenever activity occurs on that repo.

-GitHub Unwatch -
-

You can stop watching that repo by just clicking on the now-labeled Unwatch link again, and choosing Participating and @mentions to toggle it back to Unwatch.

-
-
-
-

Stop spamming me, GitHub!

-

It’s easy to become overwhelmed with email from one or more repos that you are following and/or participating in! In this case, you may wish to disable email notifications. -In order to set your notification settings, go to https://github.com/settings/notifications. You can, for example, uncheck the Email boxes to cease receiving notifications that way:

-GitHub Notification Settings -
-

If you turn email notifications off, get in the habit of clicking on the Notifications icon when logged into GitHub:

-GitHub Notifications -
-

You can click on the Notifications icon and scroll through all notifications from repos that you opted into receiving notifications from:

-GitHub Notification Browser -
-

Use the Filter notifications control to display only those that meet certain criteria. For example, say you only wanted to view topics related to the MetPy repo:

-GitHub Notification Filter -
-
-

Tip:

-

In the list of notifications, you can unsubscribe as shown below.

-
-GitHub Notification Unsubscribe -
-
-
-

Summary

-
    -
  • GitHub uses secure tokens to enable write (and sometimes read) access to GitHub repositories.

  • -
  • You can opt-in to notifications on a repo. The default, which can be easily changed, is to receive email.

  • -
-
-

What’s Next?

-

In the next section, we will learn the basics of version control using command-line git.

-
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/github-workflows.html b/_preview/419/foundations/github/github-workflows.html deleted file mode 100644 index 7d8e534ce..000000000 --- a/_preview/419/foundations/github/github-workflows.html +++ /dev/null @@ -1,1422 +0,0 @@ - - - - - - - - GitHub Workflows — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - Git Logo -
-

GitHub Workflows

-

A workflow is a series of activities or tasks that must be completed sequentially or parallel to achieve the desired outcome. Here we outline two different GitHub workflows that take you through the steps leading up to opening a Pull Request.

-
-

Overview:

-
    -
  1. GitHub workflows overview

  2. -
  3. Git Feature Branch Workflow

  4. -
  5. Forking workflow

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub

Necessary

GitHub Repositories

Necessary

Cloning and Forking

Necessary

Basic Version Control with git

Necessary

Issues and Discussions

Recommended

Branches

Necessary

Pull Requests

Necessary

Reviewing Pull Requests

Recommended

-
    -
  • Time to learn: 60 minutes

  • -
-
-
-
-

GitHub workflows

-

GitHub, together with Git, are powerful tools for managing and -collaborating on all kinds of digital assets, such as software, -documentation, and even manuscripts for research papers. Like other -complex software environments, often these tools can be employed -in many different ways to accomplish the same goal. In order to -effectively and consistently use Git and GitHub, over the years a -variety of best practices have evolved for supporting different -modes of collaboration. Collectively these different models, or -recipes, are referred to as workflows.

-

A typical sequence of workflow steps consists of the following:

-
    -
  1. A contributor clones a personal remote repository, creating a local copy

  2. -
  3. The contributor creates a new branch in their local repository

  4. -
  5. The contributor makes changes to the branch and commits them to -their local repository

  6. -
  7. The contributor pushes the branch to a remote repository

  8. -
  9. The contributor submits a PR via GitHub

  10. -
-

The sequence of steps -outlined above provides a general framework for submitting a PR. -But the precise set of steps is highly dependent on the choice of -workflow for a given project. In this chapter we describe Pull -Requests for two commonly used workflows: The Git Feature Branch -Workflow and the Forking Workflow. The former is simpler and often -used by teams when everyone on the team is an authorized contributor -to the destination repository. I.e. all of the contributors have -write access to the remote repository hosted by GitHub. The latter -is typically what is needed to contribute to external projects for -which the contributor is not authorized (i.e. does not have write -access) to make changes to the destination repository. We briefly -describe both workflows below, and include the steps necessary to -make a PR on each.

-
-
-

Git Feature Branch Workflow

-

The Git Feature Branch Workflow is one of the simplest and oldest -collaborative workflows that is used for small team projects. The -key idea behind this workflow, which is also common to the Forking -Workflow, is that all development (all changes) should take place -on a dedicated Git feature branch, not the main (historically -referred to as master) branch. The motivation behind this is that -one or more developers can iterate over a feature branch without -disturbing the contents of the main branch. Consider using the Git -Feature Branch Workflow for GitHub’s most widely used purpose, -software development. Software modifications are liable to introduce -bugs. Isolating them to a dedicated branch until they can be fixed -ensures that a known, or official, version of the software is always -available and in working order.

-
-

Note

-

Avoiding making edits directly on the main branch is considered best practice for most workflows and projects!

-
-
-

Working with the Git Feature Branch Workflow

-

This model assumes a single, remote GitHub repository with a branch -named main, that contains the official version of all of the digital -assets, along with a history of all of the changes made. When a -contributor wishes to make changes to the remote repository, they -clone the repo and create a descriptively named feature branch, -such as my-new-feature or perhaps issue-nnn, where nnn is the -number of an issue opened on the repository that this new feature -branch will address. Changes by the contributor are then made to -the feature branch in a local copy of the repository. When ready, -the new branch is pushed to the remote repository.

-

At this point, -the new branch can be viewed, discussed, and even changed by -contributors with write access to the remote repository. When the -author of the feature branch thinks the changes are ready to be -merged into main on the remote repository, they create a PR. The -PR signals the project maintainers that the contributor would like -to merge their feature branch into main, and invites review of the -changes made in the branch. GitHub simplifies the process of viewing -the changes by offering a variety of ways to see context differences -(diffs) between main and the feature branch. Discussion between -the reviewers and the contributor inside a PR discussion forum -occurs in the same way that discussion over GitHub Issues takes -place inside a discussion forum associated with a particular issue. -If additional changes are requested by the reviewers, these can be -made by the contributor in their local repository, committed, and -then pushed to the remote using the same processes they used with -the initial push. Once reviewers are satisfied with the changes, a -project maintainer can merge the feature branch with main.

-
-

Cloning the remote repository

-

If you don’t have a local copy of the remote repository, you’ll want -to create one by cloning the -remote -to your local computer. This can be done with the git command line -tools and the general form of the command looks like this:

-
git clone repository-url local-directory-name
-
-
-

Where repository-url is the URL for the GitHub repo that you want -to clone, and local-directory-name is the directory path on your -local machine into which you want to create the clone. The local -directory need not already exist. The clone command will create the -local directory for you. If you don’t know the URL for your -repository, navigate your web browser to your GitHub repository, -and click on the Code button. The URL will be displayed.

-

For example, let’s clone the Project Pythia sandbox repository:

-
git clone https://github.com/ProjectPythia/github-sandbox.git
-
-
-

Note, we did not specify a local-directory_name here, so git will -use the base name of the repository_url, “github-sandbox” as -the local directory.

-
-
-

Start with the main branch

-

Continuing with our example above, make sure you are on the main -branch and that it is up to date with the remote repository main:

-
cd github-sandbox
-git checkout main
-git pull
-
-
-

You should see output that looks like:

-
Already on 'main'
-Already up to date.
-
-
-

Remember you can read more about GitHub branches in our previous chapter.

-
-
-

Create a new branch

-

Create a separate branch for every new capability you work on:

-
git checkout -b my-new-feature
-
-
-

This command will create a new branch named my-new-feature, if it -doesn’t exist already, or switch to the existing branch if it does. -Either way, any changes you make will occur in the branch my-new-feature, -not in main. The output should look something like:

-
Switched to a new branch 'my-new-feature'
-
-
-
-
-

Make changes and commit

-

Next, we’ll make changes and commit them to the my-new-feature branch in -the local git repository.

-

Use your favorite editor to edit the file “sample.py”. Add the line:

-
print ("Do you like to rock the party?")
-
-
-

after the existing print statement in the file.

-

Run the command git status and look at the output. You should see -something like:

-
On branch my-new-feature
-Changes not staged for commit:
-  (use "git add <file>..." to update what will be committed)
-  (use "git restore <file>..." to discard changes in working directory)
-	modified:   sample.py
-
-no changes added to commit (use "git add" and/or "git commit -a")
-
-
-

Another helpful command is git diff, which should give output -that looks like:

-
diff --git a/sample.py b/sample.py
-index b2a3b61..bf89419 100644
---- a/sample.py
-+++ b/sample.py
-@@ -1,5 +1,6 @@
- """This is a text file that contains a sample Python script"""
- print ("Hello, Python learners!")
-+print ("Do you like to rock the party?")
- a = 2
- b = 8
-
-
-

It’s probably obvious that git status will show you which files have been modified and are -ready to be committed, while git diff will show you how your changes -to my-new-feature branch differ from the main branch in the local -repository. Once you are ready, commit your changes to the local -repository:

-
git add sample.py
-git commit -m "having fun yet?" .
-
-
-

After a successful commit you should see a message like:

-
[my-new-feature 69162bc] having fun yet?
- 1 file changed, 1 insertion(+)
-
-
-
-
-

Push the feature branch to the remote repository

-

After running git commit your changes have been captured in your -local repository. But most likely only you can see them, and if -your local file system fails your changes may be lost. To make your -changes visible to others, and safely stored on your remote GitHub -repository, you need to push them. However, remember at the beginning -of this section we said that the Git Feature Branch Workflow works -when you have write access to the remote repository? Unless you are -a member of Project Pythia you probably don’t have write access to -the github-sandbox remote repo. So you won’t be able to push your -changes to it. That’s OK. We can still run the push command. It won’t -break anything. In the next section on Forking Workflow we will -discuss how to make changes on remote repositories that you do NOT -have write access to, such as the one we’re using in this example. Here -is the push command that we expect to fail:

-
git push --set-upstream origin my-new-feature
-
-
-

You should get a helpful error message like:

-
remote: Permission to ProjectPythia/github-sandbox.git denied to clyne.
-fatal: unable to access 'https://github.com/ProjectPythia/github-sandbox.git/': The requested URL returned error: 403
-
-
-
-

The use of the ‘–set-upstream’ option is a one-time operation when -you push a new branch. Later, if you want to push subsequent changes -to the remote you can simply do:

-
git push
-
-
-

If you are feeling unsatisfied about not having git push succeed, there -is a simple solution: create a GitHub repository owned by you. The -GitHub Quickstart guide provides an excellent tutorial on how to -do this.

-
-
-

Making a Pull Request

-

Finally, after cloning a remote repository, creating a feature -branch, making your changes, committing them to your local repository, -and pushing your commits back to the remote repository, you are now -ready to issue a PR requesting that the remote repository maintainers -review your changes for potential merger into the main branch on -the remote. This final action must be performed from within your -web browser. After -navigating to your repo do the following:

-
    -
  1. Click on “Pull Requests” in the top navigation bar

  2. -
  3. Click on “New Pull Request”

  4. -
  5. Under “Compare changes”, make sure that base is set to main, and compare is set to the name of your feature branch, my-new-feature

  6. -
  7. Click on “Create Pull Request”

  8. -
  9. A PR window should open up. Provide a descriptive title, and any helpful comments that you want to communicate with the reviewers

  10. -
  11. Click on “Create Pull Request” in the PR window.

  12. -
-

That’s it! You’re done! Sit back and wait for comments from reviewers. -If changes are requested, simply repeat the steps above. Once your -PR is merged you’ll receive notification from GitHub.

-
-
-

Safety tip on synchronization

-

Over time your local repository will diverge from the remote. Before -starting on a new feature, or if the main branch on remote may have -been updated while you were working on my-new-feature, it is a good -idea to periodically sync up with the remote main. Make sure all -of your changes to my-new-feature have been committed to the local -repository, and then do:

-
git checkout main
-git pull
-git checkout my-new-feature
-git merge main
-
-
-
-
-
-
-

Forking Workflow

-

The Git Feature Branch Workflow described above, along with the -steps needed to submit a PR, work when you have write access to the -remote repository. But as we saw, if you don’t have write access -you will not be able to push your changes to the remote repo. So, -if you are contributing to an open source project, such as Project -Pythia for example, a slightly different workflow is required. -The Forking Workflow is the one most commonly used for public open -source projects. The primary difference between the Forking Workflow -and the Git Feature Branch Workflow is that with the former, two -remote repositories are involved: one managed by the developers of -the project that you wish to contribute to, and one owned by you. -To help keep things clear we will refer to these remotes as the -upstream repository and the personal repository, respectively. Not -surprisingly, the personal repository will be a clone of the project -repository that you own and can push changes too. The personal -repository must be public, so that the maintainers of the upstream -repository can pull changes from it. Other than a couple of additional -steps required at the beginning and the end, the process of submitting -a PR when using the Forking Workflow is identical to that of the -Git Feature Branch Workflow. The basic steps are as follows:

-
    -
  1. A contributor forks the upstream repository, creating a remote clone that is owned by the contributor: the personal repository

  2. -
  3. The contributor then clones the newly created personal remote repository, creating a local copy. Yup, that is two clones.

  4. -
  5. The contributor creates a new branch in their local repository

  6. -
  7. The contributor makes changes to the branch and commits them to their local repository

  8. -
  9. The contributor pushes the branch to their personal remote repository that was created in step 1

  10. -
  11. The contributor submits a PR via GitHub to the upstream repository

  12. -
-

Note that steps 2 through 5 are identical to steps 1 through 4 for -the Git Feature Branch Workflow. Hence, here we only discuss the -first step, and last step.

-
-

Forking the upstream repository

-

GitHub makes it really easy to fork a remote repository. Simply -navigate your web browser to the upstream repository that you want -to fork, and click on Fork. GitHub will create a clone of the -upstream repository in the remote destination selected by you on -GitHub, and will then redirect your browser to the newly created -forked, personal repository. The personal repository is owned by -you. Any changes made here will not impact the upstream repository -until you are ready to submit a PR. Let’s try it. Follow -the steps under Forking a repository here.

-
-
-

Clone, branch, change, commit, push

-

The next steps are the same as described above for the Git Feature -Branch Workflow. Clone a local copy of the newly created remote, -personal repository, create a feature branch, make your changes, -commit your changes, and push the new branch with your commits to your personal repository.

-
-
-

Making a Pull Request

-

Once the new feature branch has been pushed to the contributor’s -personal repository, a PR can be created that asks the maintainers -of the upstream repository to merge the contents of the feature -branch on the contributor’s repository into the main branch on the -upstream repository. This step is remarkably similar to making a -PR in the Git Feature Branch Workflow. The only difference is that -the contributor navigates their browser to the upstream, remote -repository, not the personal remote, and initiates the PR there. -Specifically, the following steps are once again followed, but -performed on the upstream remote:

-
    -
  1. Click on “Pull Requests” in the top navigation bar

  2. -
  3. Click on “New Pull Request”

  4. -
  5. Under “Compare changes”, make sure that base is set to main, and compare is set to the name of your feature branch, my-new-feature

  6. -
  7. Click on “Create Pull Request”

  8. -
  9. A PR window should open up. Provide a descriptive title, and any helpful comments that you want to communicate with the reviewers

  10. -
  11. Click on “Create Pull Request” in the PR window.

  12. -
-
-
-

Safety tip on synchronization

-

Just like with the Git Feature Branch Workflow model, over time -your local repository will diverge from the remote(s). Before -starting on a new feature, or if the main branch on remote may have -been updated while you were working on my-new-feature, it is a good -idea to periodically sync up with the remote main. When working -with forks things get a little more complicated than when only a -single remote is involved. Before syncing with the upstream remote -you must first configure your local repository by running the -following commands from within your local copy of the repo:

-
git remote -v
-
-
-

This should produce an output that looks similar to the following:

-

origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) -origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)

-

Next, specify a new remote upstream repository that will be synced with the fork.

-
git remote add upstream upstream-url
-
-
-

Where upstream-url is the URL of the upstream repository.

-

Finally, rerun the git remote -v command and you should see output -that looks like this:

-
origin    https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
-origin    https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
-upstream  https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (fetch)
-upstream  https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git (push)
-
-
-

After performing the above steps, you can then synchronize your -local repository with the upstream remote by running the following:

-
git fetch upstream
-git checkout main
-git merge upstream/main
-
-
-
-
-
-
-

Summary

-
    -
  • The steps that lead up to -the PR depend your GitHub Workflow.

  • -
  • Two commonly used GitHub Worflows are Git Feature Branch Workflow and -Forking Workflow. The former is appropriate for teams of collaborators -where everyone has write access to the GitHub repository. The latter -is commonly used when a developer wishes to contribute to a public GitHub -project for which they do not have write access to the repository.

  • -
-
-

What’s Next?

-

In the next lesson we will put the Forking Workflow to work and show you -how to use it to contribute to Project Pythia.

-
-
-
-

References

-
    -
  1. Atlassian’s tutorial on workflows

  2. -
  3. GitHub’s Collaborating with Pull Requests

  4. -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/review-pr.html b/_preview/419/foundations/github/review-pr.html deleted file mode 100644 index 663e65dc5..000000000 --- a/_preview/419/foundations/github/review-pr.html +++ /dev/null @@ -1,1047 +0,0 @@ - - - - - - - - Reviewing Pull Requests — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - GitHub Logo -
-

Reviewing Pull Requests

-

Pull Requests (PRs) are typically reviewed by collaborators before being merged in to the main project branch. Many people feel overwhelmed, or feel as though their skills are lacking, when asked to perform their first PR review. If you find yourself in this or a similar situation, the examples in this tutorial can be quite helpful. With the help of this tutorial, anyone can quickly learn the basics of reviewing PRs, which can boost collaboration and productivity in any project hosted on GitHub. This tutorial also contains useful tips on how to effectively review a PR in many different situations.

-
-

Overview:

-

This tutorial covers the following topics:

-
    -
  1. What is a Pull Request Review?

  2. -
  3. Requesting Pull Request Reviews

  4. -
  5. Ways to View a Pull Request

  6. -
  7. Providing a Pull Request Review

  8. -
  9. What to Look for When Reviewing

  10. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

What is GitHub

Necessary

GitHub Repositories

Necessary

Cloning and Forking

Necessary

Basic Version Control with git

Necessary

Issues and Discussions

Recommended

Branches

Necessary

Pull Requests

Necessary

-
    -
  • Time to learn: 30 minutes

  • -
-
-
-
-

What is a Pull Request Review?

-

A PR Review is an opportunity for a team member to look through proposed file changes and request changes before merging these changes into the primary project branch (usually called “main”), or another important project branch. The reviewer may attempt to acquire information about the content of the PR by asking precise questions. They may also suggest edits to the content, either explicitly, such as changes to specific lines of code, or implicitly, such as a request for more detailed documentation. Before the PR is merged, the author of the PR content should attempt to satisfy all requests in the review. In fact, if the branch being updated by the PR has active protections, the author may be required to satisfy some such requests.

-
-
-

Requesting Pull Request Reviews

-

Most people learning GitHub are confused about when to request review on a PR they create. The answer is that review should be requested when a PR is (or is likely) ready to merge into the primary project branch (or another important project branch).

-

To start the review process, navigate to the right sidebar menu that appears when viewing your PR. Then, under “Reviewers”, select the gear icon, and then select or enter a GitHub user’s ID for whom you would like to approve your work. If the files listed in the PR are owned or recently edited by specific reviewers, GitHub may automatically suggest the user IDs of those reviewers.

-

Request Reviews

-
-

Did you know?

-

It is possible to automate this process with a CODEOWNERS file and GitHub actions.

-
-

To learn more about any topic relating to requesting a PR review, including topics such as CODEOWNERS files, please review the official Requesting a Pull Request Review Documentation.

-
-
-

Ways to View a Pull Request

-

If you are unfamiliar with the process of reviewing a PR, the material in this tutorial section will describe the process in detail. The first step to reviewing a PR is to review the files changed by the PR. However, before reviewing the changed files, it is very helpful to view these files in a meaningful way.

-

The first useful way to view changed files in a PR is through the PR’s “Files Changed” tab. On this tab, added content is displayed in green, while removed content is displayed in red.

-

Reviewing Files Changed

-

This method of viewing changed files works well for most types of code; however, if the code is designed to be rendered as a webpage, Jupyter Notebook, or other similar format, a different method of viewing is recommended.

-

There are some standard methods of easily viewing Jupyter Notebooks and rendered webpages in GitHub; these are commonly used by repositories with large amounts of this type of content. GitHub actions can be used to provide previews of the rendered content; there are also third-party services, such as ReviewNB, that allow for viewing of this content. Also, it is important to know that when viewing a preview of webpage content provided by GitHub actions, using any absolute links in the preview will take the web browser out of the preview and out of GitHub.

-

Another popular way to easily view any type of PR content is to locally check out the PR branch. This can be accomplished by cloning the GitHub repo and switching in the local clone to the branch containing the PR. Viewing a PR through a local clone allows the reviewer to use any applications available through the terminal, including code editors, Jupyter applications, and Web browsers, to view the changed files quickly and easily. For more information on this process, please review the documentation GitHub provides on checking out pull requests locally.

-

As described above, there are many ways to view changed files in a GitHub PR, including local clones, GitHub action previews, and services such as ReviewNB. However, these services may not detail the changes to the files listed in the PR; therefore, the “Files Changed” tab should be the main resource for deciding where to focus a review.

-
-
-

Providing a Pull Request Review

-

There are many ways to provide a PR review. The most basic of these is to comment on specific lines. This type of review can be performed through the “Files Changed” tab. By clicking on the “+” icon next to a line of code, the reviewer can provide a comment, and either start a new review, or simply link the comment to the line of code.

-

Inline Reviews

-

If the review consists mainly of comments relevant to specific lines of code, this review method is preferred.

-

If you are the reviewer, and the review consists mainly of small edits that you can perform yourself, this is also the preferred review method. To start one of these small edits, open a comment on the line of code to be edited, as described above. You can then suggest the edit by clicking on the “±” icon, circled in red in the screenshot below. This icon automatically populates the comment box with the line of code and formats it with Markdown. You must replace the line of code in the comment box with the edited version, then link the comment or start a new review as described above.

-

Review Suggestions

-

If the review is more complex than simple edits to specific lines of code, you can find more detailed reviewing tools in the Review Changes menu in the top right. This menu contains a comment box, as well as options for specific types of review. These options are described in detail after the informational screenshot below.

-

Approving Review

-
    -
  • The “Comment” option allows the reviewer to provide simple comments or questions on the PR before the review is finished and the PR merged. Please note that comments and questions that may hinder the PR merge process should not be handled in this way.

  • -
  • The “Approve” option is used to indicate that the reviewer wholeheartedly approves the content changes in the PR, and that these content changes should be merged into an important project branch as quickly as possible. This option is also known as the LGTM (let’s get this merged) option.

  • -
  • The “Request changes” option is used to indicate that the content changes contain one or more elements that require improvement or resolution before the PR can be merged.

  • -
-

After providing review text in the comment box, and selecting a review type, make sure to click on the “Submit Review” button to finish the review.

-
-
-

What to Look for When Reviewing

-

There are specific elements of PRs that are more commonly prioritized during a review. To address these elements, most reviewers perform the following tasks:

-
    -
  • Look at the description and linked GitHub issue to make sure the PR addresses the issue

  • -
  • Attempt to figure out the details of the content changes in the PR, and the purpose of those changes

  • -
  • Look at the content for spelling errors

  • -
  • Provide feedback on the code itself

    -
      -
    • Does the code contain input checks, debug statements, verification, or the like?

    • -
    • If the code contains any of these checks, are they sufficiently robust?

    • -
    • Is the code written in a way that allows for understanding of its purpose?

    • -
    • As the reviewer, are you familiar with a way to simplify the code, or make the code more efficient?

    • -
    • Does the code contain identifiers with conflicting or confusing names that need correcting?

    • -
    • Do you, as the reviewer, notice any other issue with the code that may need to be dealt with in your review?

    • -
    -
  • -
  • If any of the content changed by the PR is meant to be rendered (e.g., as a webpage or Jupyter Notebook), preview this content to check for issues with design and functionality

  • -
  • Finally, try to clearly state not only the changes made in your review, but also the issues not changed by your review. It is perfectly acceptable to not cover every item in this list; however, it is good practice to include the items covered in the review, and the nature of these changes. Most teams that manage a GitHub repository appreciate the inclusion of opinion and detail in a PR review.

  • -
-
-
-
-

Summary

-
    -
  • PR Reviews safeguard the primary project branch (and other important project branches) in a GitHub repository. These reviews require contributors in a repository to perform a detailed examination of changes to code and other files. The files remain unchanged until these examinations are finished.

  • -
  • There exist certain standards pertaining to PR reviews; in addition to following these standards, it is important to provide detail on the basis of your review.

  • -
-
-

What’s Next?

-

The next tutorial will cover standards and other details about GitHub Workflows.

-
-
-
-

Resources and References

-
    -
  1. GitHub’s tutorial on Collaborating with Pull Requests

  2. -
  3. GitHub’s tutorial on Requesting a Pull Request Review

  4. -
  5. GitHub’s tutorial on Checking Out Pull Requests Locally

  6. -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/github/what-is-github.html b/_preview/419/foundations/github/what-is-github.html deleted file mode 100644 index 89cc90de7..000000000 --- a/_preview/419/foundations/github/what-is-github.html +++ /dev/null @@ -1,1053 +0,0 @@ - - - - - - - - What is GitHub? — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- - GitHub Logo -
-

What is GitHub?

-
-

Overview:

-
    -
  1. What is GitHub?

  2. -
  3. No experience necessary!

  4. -
  5. Free and open-source software (FOSS)

  6. -
  7. Version control systems (VCS)

  8. -
  9. GitHub = FOSS + VCS + Web

  10. -
  11. Register for a free GitHub account

  12. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

None

-
    -
  • Time to learn: 15 minutes

  • -
-
-
-
-

What is GitHub?

-

GitHub is a web-based platform for the dissemination of free and open-source software.

-

If you are reading this lesson, you are already using GitHub, as that is where Project Pythia hosts its content!

-

GitHub provides the following:

-
    -
  1. Version control for free and open-source software and other digital assets

  2. -
  3. Project discussion forums

  4. -
  5. DevOps to facilitate building and testing software

  6. -
  7. Bug reporting, patching, and tracking

  8. -
  9. Documentation hosting

  10. -
  11. An environment that fosters collaboration

  12. -
-

Although GitHub can host any digital asset, the most common use case for GitHub is for individuals or organizations to house repositories of free and open-source software:

-
-
-

No experience necessary!

-

You do not need to be an experienced software developer or be proficient in version control to make use of GitHub! Perhaps, though, you have used a particular package (e.g., Xarray or Matplotlib) and have had questions about its usage, noticed a bug, or had an idea for a new feature for the package! You can participate in a project’s development via GitHub the same way you might have interacted with its developers via email in the past.

-
-
-

Free and open-source software (FOSS)

-

Much of what we term the scientific Python software ecosystem consists of free and open-source software. Often abbreviated as FOSS, this means:

-
    -
  1. The software is free of charge, and

  2. -
  3. The various files which contain the software code are publicly available.

  4. -
-
-

Did you know?

-

The Python language itself is an example of FOSS!

-
-

FOSS is nothing new. For example, the Linux kernel source code has been available to download for many years.

-
-

Free \(\neq\) open source!

-

Just because a software package may be free does not mean that its source code is open! For example, although Nvidia makes its video drivers available for free download, the source code for those drivers is proprietary.

-
-

Arguably, the greatest advantage of open-source software is that it enables collaborative sharing, and thus community feedback.

-

Types of community input may include the following:

-
    -
  1. Issues: usage questions, bug reports, feature requests

  2. -
  3. Pull requests: a user can ask that that their changes/additions be incorporated into the project

  4. -
  5. Discussions: a community forum on the open source project

  6. -
-
-
-

Version control systems (VCS)

-

We will discuss version control in more detail later in this series, but the need to track and manage changes to a project, especially one that involves software, has long been known. Over the years, FOSS developers have used VCS such as cvs, svn, and most recently, git. All of these systems are command-line tools.

-
-
-

FOSS and VCS on the Internet

-

A successful FOSS project needs to be accessible via the web. As mentioned before, the Linux kernel and the Python language have long been available using first-generation remote access protocols such as FTP and HTTP, and SSH. Later, VCS tools such as cvs and svn established their own TCP protocols for remote access. With the advent of git, web-based services that supported HTTP(S) and SSH sprung up. Each of these VCS leverages the concept of a particular FOSS project as a code repository.

-
-

Did you know?

-

Linus Torvalds, the original developer (and still the lead maintainer) of Linux, is also the original developer of Git!

-
-
-

Stay tuned!

-

We will discuss version control and the use of Git via the command line later in this series.

-
-
-
-

FOSS + VCS + Web = GitHub

-

Perhaps the most popular web-based platform that uses Git for FOSS VCS is GitHub. GitHub hosts all of the Python software packages that Project Pythia covers as code repositories (we’ll use the term Git repo, or more generally just repo henceforth to represent a GitHub code repository).

-

For example, here is a screenshot from Xarray’s GitHub Git repo:

-Xarray GitHub -
-

Note

-

The above screenshot is from one moment in time. When you visit the Xarray GitHub link above, it will no doubt look different!

-
-
-
-

Register for a free GitHub account

-

While one can freely browse GitHub repositories such as Xarray anonymously, it’s necessary to log into a unique (and free) user account in order to take advantage of GitHub’s full capabilities, such as:

-
    -
  1. Opening Issues and Pull Requests

  2. -
  3. Participating in Discussions

  4. -
  5. Hosting your own repository

  6. -
-

Your next step (if you haven’t already) should be to register for your free GitHub account. As with many online services, you will specify a user ID, password, and email address to use with your account.

-

To do so, simply point your browser to the GitHub sign-up page:

-GitHub Signup -

While GitHub offers paid options, a free account is typically all that is needed!

-
-
-
-

Summary

-
    -
  • GitHub serves as a web-based platform for digital assets, particularly FOSS.

  • -
  • GitHub uses Git as its version control system.

  • -
  • You can set up a free user account on GitHub.

  • -
-
-

What’s Next?

-

In the next lesson, we will explore some GitHub repositories.

-
-
-
-

References

-
    -
  1. GitHub (Wikipedia)

  2. -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/how-to-run-python.html b/_preview/419/foundations/how-to-run-python.html deleted file mode 100644 index 5311efc81..000000000 --- a/_preview/419/foundations/how-to-run-python.html +++ /dev/null @@ -1,1000 +0,0 @@ - - - - - - - - Installing and Running Python — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Installing and Running Python

-
-
-

Overview

-

This section provides an overview of different ways to run Python code, and quickstart guides for:

-
    -
  1. Choosing a Python platform

  2. -
  3. Installing and managing Python with Conda

  4. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Why Python?

Helpful

-
    -
  • Time to learn: 20 minutes

  • -
-
-
-
-

Choosing a Python Platform

-

There is no single official platform for the Python language. Here we provide a brief rundown of 3 popular platforms:

-
    -
  1. The terminal,

  2. -
  3. Jupyter notebooks, and

  4. -
  5. IDEs (integrated development environments).

  6. -
-

Here we hope to provide you with enough information to understand the differences and similarities between each platform, so that you can make the best choice for your work environment and learn along effectively, regardless of your Python platform preference.

-

In general, it is always best to test your programs in the same environment in which they will be run. The biggest factors to consider when choosing your platform are:

-
    -
  • What are you already comfortable with?

  • -
  • What are the people around you using (peers, coworkers, instructors, etc.)?

  • -
-
-

Terminal

-

For learners who are familiar with basic Linux commands and text editors (such as Vim or Nano), running Python in the terminal is the quickest route straight to learning Python syntax without the covering the bells and whistles of a new platform. If you are running Python on a supercomputer, through an HTTP request or SSH tunneling, you might want to consider learning in the terminal.

-

How to Run Python in the Terminal

-
-
-

Jupyter Notebooks

-

We highly encourage the use of Jupyter notebooks: a free, open-source, interactive tool running inside a web browser that allows you to run Python code in “cells.” This means that your workflow can alternate between code, output, and even Markdown-formatted explanatory sections that create an easy-to-follow analysis or “computational narrative” from start to finish. Jupyter notebooks are a great option for presentations or learning tools. For these reasons, Jupyter is very popular among scientists. Most lessons in this book will be taught via Jupyter notebooks.

-

How to Run Python in a Jupyter Session

-
-
-

Other IDEs

-

If you code in other languages, you might already have a favorite IDE that will work just as well in Python. Spyder is a Python specific IDE that comes with the Anaconda download. It is perhaps the most familiar IDE if you are coming from languages such as Matlab that have a language specific platform and display a list of variables. PyCharm and Visual Studio Code are also popular IDEs. Many IDEs offer support for terminal execution, scripts, and Jupyter display. To learn about your specific IDE, visit its official documentation.

-

We recommend eventually learning how to develop and run Python code in each of these platforms.

-
-
-
-

Installing and managing Python with Conda

-

Conda is an open-source, cross-platform, language-agnostic package manager and environment management system that allows you to quickly install, run, and update packages within your work environment(s). Conda is a vital component of the Python ecosystem. Understanding it is important, regardless of the platform you chose to run your Python code.

-

Learn more about Conda here

-
-
-
-

Summary

-

Python can be run on many different platforms. You may choose where to run Python based on a number of factors. The tutorials in this book will be formatted as Jupyter Notebooks.

- -
-
-

Resources and References

- -
-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/jupyter.html b/_preview/419/foundations/jupyter.html deleted file mode 100644 index 6009c80ea..000000000 --- a/_preview/419/foundations/jupyter.html +++ /dev/null @@ -1,1006 +0,0 @@ - - - - - - - - Python in Jupyter — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Python in Jupyter

-
-
-

Overview

-

You’d like to learn to run Python in a Jupyter session. Here we will cover:

-
    -
  1. Installing Python in Jupyter

  2. -
  3. Running Python code in Jupyter

  4. -
  5. Saving your notebook and exiting

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Installing and Running Python

Helpful

-
    -
  • Time to learn: 20 minutes

  • -
-
-
-
-

Installing Python in Jupyter

-

To run a Jupyter session, you will need to install some necessary packages into your Conda environment.

-

Install miniconda by following the instructions for your machine.

-

Learn more about Conda here

-

Next, create a Conda environment with Jupyter Lab installed. In the terminal, type:

-
$ conda create --name pythia_foundations_env jupyterlab
-
-
-

Test that you have installed everything correctly by first activating your environment and then launching a Jupyter Lab session:

-
$ conda activate pythia_foundations_env
-$ jupyter lab
-
-
-

Or you can install the full Anaconda, and select LAUNCH under the Jupyter panel in the GUI.

-

Anaconda Navigator

-

In both methods, a new window should open automatically in your default browser. You can change the browser when launching from the terminal with (for example):

-
jupyter lab —browser=chrome
-
-
-
-
-

Running Python in Jupyter

-
    -
  1. With your Conda environment activated and Jupyter session launched (see above), create a directory to store our work. Let’s call it pythia-foundations.

    -

    Jupyter GUI

    -

    You can do this in the GUI left sidebar by clicking the new-folder icon. If you prefer to use the command line, you can access a terminal by clicking the icon under the “Other” heading in the Launcher.

    -
  2. -
  3. Create a new mysci.ipynb file within the pythia-foundations folder:

    -

    Do this in the GUI on the left sidebar by clicking the “+” icon.

    -

    This will open a new launcher window where you can select a Python kernel under the “Notebooks” heading for your project. You should see “Python 3” as in the screenshot above. Depending on the details of your system, you might see some additional buttons with different kernels.

    -

    Selecting a kernel will open a Jupyter notebook instance and add an untitled file to the left sidebar navigator, which you can then rename to mysci.ipynb.

    -

    Select “Python 3” to use the Python version you just installed in the pythia_foundations_env conda environment.

    -
  4. -
  5. Change the first notebook cell to include the classic first command: printing, “Hello, world!”.

    -
    print("Hello, world!")
    -
    -
    -
  6. -
  7. Run your cell with Shift+Enter and see that the results are printed below the cell.

    -

    Jupyter - Hello World

    -
  8. -
-

Congratulations! You have just set up your first Python environment and run your first Python code in a Jupyter notebook.

-
-
-

Saving your notebook and exiting

-

When you are done with your work, it is time to save and exit.

-

To save your file, you can click the disc icon in the upper left Jupyter toolbar or use keyboard shortcuts.

-

Jupyter allows you to close the browser tab without shutting down the server. When you’re done working on your notebook, it’s important to click the “Shutdown” button on the dashboard to free up memory, especially on a shared system.

-

Then you can quit Jupyter by:

-
    -
  • clicking the “Quit” button on the top right, or

  • -
  • typing exit into the terminal

  • -
-

Alternatively you can simultaneously shutdown and exit the Jupyter session by typing -Ctrl+C in the terminal and confirming that you do want to -“shutdown this notebook server.”

-
-
-
-

Summary

-

Jupyter notebooks are a free, open-source, interactive tool running inside a web browser that allows you to run Python code in “cells.” To run a Jupyter session you will need to install jupyterlab into your Conda environment. Jupyter sessions need to be shutdown, not just exited.

- -
-
-

Resources and References

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/jupyterlab.html b/_preview/419/foundations/jupyterlab.html deleted file mode 100644 index 61b21f907..000000000 --- a/_preview/419/foundations/jupyterlab.html +++ /dev/null @@ -1,1320 +0,0 @@ - - - - - - - - JupyterLab — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -
-

JupyterLab

-
-
-

Overview

-

JupyterLab is a popular web application on which users can create and write their Jupyter Notebooks, as well as explore data, install software, etc. This section will introduce the JupyterLab interface and cover details of JupyterLab Notebooks.

-
    -
  1. Set Up

  2. -
  3. The JupyterLab Interface

  4. -
  5. Running JupyterLab Notebooks

  6. -
-
-
-

Prerequisites

- - - - - - - - - - - - - - - - - -

Concepts

Importance

Notes

Getting Started with Jupyter

Helpful

Installing and Running Python: Python in Jupyter

Helpful

-
    -
  • Time to learn: 50 minutes

  • -
-
-
-
-

Set Up

-

To launch the JupyterLab interface in your browser, follow the instructions in Installing and Running Python: Python in Jupyter.

-

If, instead, you want to follow along using a provided remote JupyterLab instance, launch this notebook via Binder using the launch icon at the top of this page,

-

Binder Launch

-

and follow along from there! If launching Binder, take note of the Launcher tab in the upper-left (see interface below). Click there to find yourself in the same interface moving forward, and feel free to refer back to this tab to follow along.

-
-
-

The JupyterLab Interface

-

Go to your browser and take a look at the JupyterLab interface.

-

With a base installation of JupyterLab your screen should look similar to the image below.

-

Notice:

-
    -
  • The Menu Bar at the top of the screen, containing the typical dropdown menus: “File”, “Edit”, “View”, etc.

  • -
  • Below that is the Workspace Area (currently contains the Launcher).

  • -
  • The Launcher is a quick shortcut for accessing the Console/Terminal, for creating new Notebooks, or for creating new Text or Markdown files.

  • -
  • On the left is a Collapsible Sidebar. It currently contains the File Browser, but you can also select the Running Tabs and Kernels, the Table of Contents, and the Extensions Manager.

  • -
  • Below everything is the Information Bar, which is context sensitive. We will touch on this more.

  • -
-

Interface

-

We will now take a closer look at certain aspects and features of the JupyterLab Interface.

- -
-

Terminals

-

Let’s select the Running Tabs and Kernels Tab in the Left Sidebar and see how it changes when we’ve used the Launcher.

-

Open a Terminal in the Launcher. It should look very similar to the desktop terminal that you initially launched JupyterLab from, but is running from within JupyterLab, within your existing Conda environment, and within the directory you launched JupyterLab from (the same root folder shown in the File Browser Tab). Notice that there is now a Terminal listed in the Running Terminals Tab.

-

In the terminal you can use your usual terminal commands. For example, in the terminal window, run:

-
$ mkdir test
-
-
-

Select the File Browser Tab, refresh it, and see that your new folder is there.

-

In the Terminal Window run:

-
$ rmdir test
-
-
-

Hit refresh in the File Browser again to see that the directory is gone.

-

Terminal

-

Back with the Running Terminals and Kernels Tab open, click the “X” in your workspace to close the Terminal window. Notice that the Terminal is still running in the background! Click on the terminal in the Running Terminals and Kernels Tab to reopen it (and hit enter or return to get your prompt back). To truly close it, execute in the Terminal window:

-
$ exit
-
-
-

OR click the “X” shut down button in the Running Terminals tab.

-

Doing so will return you to the Launcher.

-
-

Info

-

The terminal is running on the local host when JupyterLab is launched locally, and remote host when invoked through Jupyter Hub.

-
-
-
-

Consoles

-

Back in the Launcher, click the “Python 3” Console button. There is only one console option right now, but you could install more kernels into your Conda environment.

-

There will be three dots while the kernel starts, then what loads looks like an IPython console. This is a place to execute Python commands in a stand alone workspace which is good for testing. Notice that the kernel started in the Running Terminals and Kernels tab!

-

Start in a “cell” at the bottom of the Console window. Type:

-
i = 5
-print(i)
-
-
-

To execute the cell, type Shift+Enter. Notice that the console redisplays the code you wrote, labels it with a number, and displays (prints) the output to the screen.

-

In the next cell, enter:

-
s = 'Hello, World!'
-print(f's = {s}')
-s
-
-
-

The first line of this code designates a string s with the value “Hello, World!”, the second line uses f-formatting to print the string, and the final line just calls up s. The last line in the cell will always be returned (its value displayed) regardless of whether you called print. Type Shift+Enter to execute the cell. Again the output is labeled (this time with a 2), and we see the input code, the printed standard-out statement, and the return statement. The “return value” and the values “printed to screen” are different!

-

Console

-

Close the window and shut down the Console in the Running Kernels tab. We’re back to the Launcher again.

-
-
-

Text Editor

-

Click on the “Text File” button in the Launcher. -Select the File Browser tab to see the new file untitled.txt you created.

-

Enter this Python code into the new text file:

-
s = 'Hello, World!'
-print(s)
-
-
-

You may notice that the file has a dot instead of an “X” where you’d close it. This indicates that the file hasn’t been saved or has unsaved changes. Save the file (“command+s” on Mac, “control+s” on Windows, or “File▶Save Text”).

-

Go to the File Browser tab, right-click the new file we created and “Rename” it to hello.py. Once the extension changes to .py, Jupyter recognizes that this is a Python file, and the text editor highlights the code based on Python syntax.

-

Now, click the “+” button in the File Browser to create a new Launcher. In the Launcher tab, click on the “Terminal” button again to create a terminal. Now you have 2 tabs open: a text editor tab and a terminal tab. Drag the Terminal tab to the right side of the main work area to view both windows simultaneously. Click the File Browser tab to collapse the left sidebar and get more real estate! Alternatively, you could stack the windows one on top of the other.

-

Run ls in the Terminal window to see the text file we just created. Execute python hello.py in the Terminal window. See the output in the Terminal window.

-

Text Editor

-

Now, let’s close the Terminal tab and shut down the Terminal in the Running Kernels tab (or execute “exit” in the Terminal itself). You should just have the Text editor window open; now we’re ready to look at Jupyter Notebooks.

-
-
-
-

Running JupyterLab Notebooks

-

There are two ways to open a Jupyter Notebook. One way is to select the File Browser Tab, click the “New Launcher” button, and select a Python 3 Notebook from the Launcher. Another way is to go to the top Menu Bar and select “File▶New▶Notebook”. JupyterLab will prompt you with a dialogue box to select the Kernel you want to use in your Notebook. Select the “Python 3” kernel.

-

If you have the File Browser Tab open, notice you just created a file called Untitled.ipynb. You will also have a new window open in the main work area for your new Notebook.

-

Let’s explore the Notebook interface:

-

There is a Toolbar at the top with buttons that allow you to Save, Create New Cells, Cut/Paste/Copy Cells, Run the Cell, Stop the Kernel, and Refresh the Kernel. There is also a dropdown menu to select the kind of cell (Markdown, Raw, or Code). All the way to the right is the name of your Kernel (which you can click to change Kernels) and a Kernel Status Icon that indicates if something is being computed by the Kernel (by a dark circle) or not (by an empty circle).

-

Below the Toolbar is the Notebook itself. You should see an empty cell at the top of the Notebook. This should look similar to the layout of the Console.

-

The cell can be in 1 of 2 modes: command mode or edit mode. -If your cell is grayed out and you can’t see a blinking cursor in the cell, then the cell is in command mode. We’ll talk about command mode more later. Click inside the box, and the cell will turn white with a blinking cursor inside it; the cell is now in edit mode. The mode is also listed on the info bar at the bottom of the page. The cell is selected if the blue bar is on the left side of the cell.

-

Notebook Interface

-

You may move the Notebook over so you can see your text file at the same time to compare, resizing the Notebook window as needed.

-
-

Code Cells

-

Click inside the first cell of the Notebook to switch the cell to edit mode. -Enter the following into the cell:

-
print(2+2)
-
-
-

Then type Shift+Enter to execute the cell.

-

You’ll see the output, 4, printed directly below your code cell. Executing the cell automatically creates a new cell in edit mode below the first.

-

In this new cell, enter:

-
for i in range(4):
-    print(i) 
-
-
-

Execute the cell. A Jupyter code cell can run multiple lines of code; each Jupyter code cell can even contain a complete Python program!

-

To demonstrate how to import code that you have written in a .py file, enter the following into the next cell:

-
import hello
-
-
-

Then type Shift+Enter.

-

This single-line import statement runs the contents of your hello.py script file, and would do the same for any file regardless of length.

-
-

Warning

- It is generally considered bad practice to include any output in a “.py” file meant to be imported and used within different Python scripts. Such a file should contain only function and class definitions. -
-You've executed the cell with the Python 3 kernel, and it spit out the output, “Hello, World!” Since you've imported the `hello.py` module into the Notebook’s kernel runtime, you can now directly look at the variable “s” in this second cell. -Enter the following in the next cell: -
hello.s
-
-
-

Hit Shift+Enter to execute.

-

Again, it displays the value of the variable s from the “hello” module we just created. One difference is that this time the output is given its own label [2] matching the input label of the cell (whereas the output from cell [1] is not labelled). This is the difference between output sent to the screen vs. the return value of the cell.

-

Let’s now import a module from the Python standard library:

-
import time
-time.sleep(10)
-
-
-

Again, hit Shift+Enter.

-

The time.sleep(10) function causes code to wait for 10 seconds, which is plenty of time to notice how the cell changes in time:

-
    -
  • The label of the cell is [*], indicating that the kernel is running that cell

  • -
  • In the top right corner of the Notebook, the status icon is a filled-in circle, indicating that the kernel is running

  • -
-

After 10 seconds, the cell completes running, the label is updated to [3], and the status icon returns to the “empty circle” state. If you rerun the cell, the label will update to [4]`.

-

Code Cells

-
-
-

Markdown Cells

-

Now, with the next cell selected (i.e., the blue bar appears to the left of the cell), whether in edit or command mode, go up to the “cell type” dropdown menu above and select “Markdown”. -Notice that the [ ] label goes away.

-

Markdown is a markup language that allows you to format text in a plain-text editor. Here we will demonstrate some common Markdown syntax. You can learn more at the Markdown Guide site or in our Getting Started with Jupyter: Markdown content. -Click on the cell and enter edit mode; we can now type in some markdown text like so:

-
# This is a heading!
-And this is some text.
-
-## And this is a subheading
-with a bulleted list in it:
-
- - one
- - two
- - three
-
-
-

Then press Shift+Enter to render the markdown to HTML.

-../_images/markdown.png -

Again, in the next cell, change the cell type to “Markdown”. To demonstrate displaying equations, enter:

-
## Some math
-
-And Jupyter’s version of markdown can display LaTeX:
-
-$$
-e^x=\sum_{i=0}^{\infty} \frac{1}{i!}x^i
-$$
-
-
-

When you are done, type Shift+Enter to render the markdown document.

-../_images/markdown_eq.png -

You can also do inline equations with a single “$”, for example

-
This is an equation: $i^4$.
-
-
-../_images/markdown_eq_inline.png -

Note that the “markdown” source code is rendered into much prettier text, which we can take advantage of for narrating our work in the Notebook!

-
-
-

Raw Cells

-

Now in a new cell selected, select “raw” from the “cell type” dropdown menu. Again, the [ ] label goes away, and you can enter the following in the cell:

-
i = 8
-print(i)
-
-
-

When you Shift+Enter the text isn’t rendered.

-

This is a way of entering text/source that you do not want the Notebook to do anything with (i.e., no rendering).

-

Raw

-
-
-

Command Mode Shortcuts

-

Now, select the “raw” cell you just created by clicking on the far left of the cell.

-

You are now in “command mode”. The up and down arrows move to different cells. Don’t hit “enter” which would switch the cell to “edit mode.” Let’s explore command mode.

-

You can change the cell type with y for code, m for markdown, or r for raw.

-

You can add a new cell above the selected cell with a (or below the selected cell with b).

-

You can cut (x), copy (c), and paste (v).

-

You can move a cell up or down by clicking and dragging.

-
-

Warning

-

Cells can be executed in any order you want. You just have to select the cell and Shift+Enter, and select the cells in any order you want. However, if you share your notebook, there is an implicit expectation to execute the cells in the order in which they are presented in the notebook. Be careful with this! If variables are reused or redefined between cells, reordering them could have unintended consequences!

-
-
-
-

Special Variables

-

Now, in the empty cell at the end, enter one underscore:

-
_
-
-
-

This is a special character that means the last cell output. Two underscores means the second to last cell output, and three underscores means the third to last output. You can also refer to the output by label with:

-
_2
-
-
-
-

Danger

-

If the cell you to refer to does not have a return value, this will raise an error.

-
-

You can equivalently use the variables Out[2] or In[2] to retrieve the output and input (as a string).

-../_images/special_vars.png -
-
-

Shell Commands

-

In the next code cell, enter the following:

-
!pwd
-
-
-

The ! allows you to write shell commands. A shell command is a command that is run by the host operating system, not the Python kernel. Executing this cell will “print the working directory” (i.e. your current directory).

-

You can even use the output of shell commands as input to Python code. For example:

-
files = !ls
-print(files)
-
-
-
-
-

Stopping & Restarting the Kernel

-

All of your commands and their output have been remembered by the kernel. However, sometimes you may get code that takes too long to execute, and you need to stop it. For example, in the next code cell, run:

-
time.sleep(1000)
-
-
-

You can stop the kernel with the square “stop” button at the top Notebook toolbar. This results in a “KeyboardInterrupt” error.

-

If you execute a notebook out of order, you can end up in a corrupted state (redefined variables, for example). To start fresh, you should restart the kernel with the circle-arrow button in the toolbar at the top. Now the kernel has forgotten everything, and you’ll need to rerun each cell.

-
-
-

Magics

-

A “magic” is a Jupyter Notebook command proceded by a % symbol. In the next code cell, enter the following:

-
%timeit time.sleep(1)
-
-
-

The “%timeit” magic is a timer that runs the command multiple times, measuring how long it takes and gathering timing statistics. Hit Shift+Enter to see it work.

-

Multiline magics work on entire cells, and these have a double-%. For example, here is a multiline version of the timeit magic:

-
%%timeit
-
-time.sleep(0.5)
-time.sleep(0.5)
-
-
-

Then press Shift+Enter to run it. This will time the entire cell.

-../_images/magics.png -
-
-
-

Shutting Down

-

Before shutting down, save your notebook with the disc icon in the Notebook toolbar. Now, close both tabs (the notebook and the text editor). You’re back to the Launcher.

-

The notebook kernel is still running, though, so go to the Running Kernels tab and shut it down.

-

Now we’re done. Go to “File▶Shut Down” to close both your browser tab and JupyterLab itself.

-
-
-

Summary

-

You are now familiar with the JupyterLab interface and running Jupyter Notebooks. Jupyter is popular for allowing you to intersperse Markdown text or equations between code cells. Jupyter offers some functionality that a Python script does not: certain keyboard shortcuts, special variables, shell scripting, and magics.

-
-

What’s next?

- -
-
- -
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/markdown.html b/_preview/419/foundations/markdown.html deleted file mode 100644 index e8b099dc1..000000000 --- a/_preview/419/foundations/markdown.html +++ /dev/null @@ -1,853 +0,0 @@ - - - - - - - - Formatted Text in the Notebook with Markdown — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -
-

Formatted Text in the Notebook with Markdown

-
-

Note

-

This content is under construction!

-
-

This section will give a tutorial on formatting text with Markdown: the simple, human-readable text language used extensively in Jupyter notebooks, GitHub Discussions, and elsewhere.

-

We will show how this is useful both in notebooks and other places like GitHub Issues.

-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/overview.html b/_preview/419/foundations/overview.html deleted file mode 100644 index 6178dc1c0..000000000 --- a/_preview/419/foundations/overview.html +++ /dev/null @@ -1,871 +0,0 @@ - - - - - - - - Overview — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
- On this page -
- -
-
-
-
-
- -
- -
-

Overview

-

This section contains cross-referenced tutorial material for foundational computing skills that one needs in order to work effectively with the open-source Scientific Python stack.

-

Familiarizing yourself with these topics first will allow a new user to get the most out the Python-specific material in the Core Scientific Python Packages section of the book!

-
-

Topics

-
    -
  • Why Python?: A brief preamble about Python’s distinguishing features.

  • -
  • Getting started with Python: A quickstart Python example, followed by detailed tutorials on how to install and run Python on your own system.

  • -
  • Getting started with Jupyter: All about the Jupyter ecosystem, which provides tools and environments for interactive, reproducible computing with Python.

  • -
  • Getting started with GitHub: Learn about the collaboration tools (GitHub) and version control software (git) that enables the open-source community.

  • -
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/quickstart.html b/_preview/419/foundations/quickstart.html deleted file mode 100644 index fdaafbb9a..000000000 --- a/_preview/419/foundations/quickstart.html +++ /dev/null @@ -1,1422 +0,0 @@ - - - - - - - - Quickstart: Zero to Python — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
-
- -
- -
-

Quickstart: Zero to Python

-

Brand new to Python? Here are some quick examples of what Python code looks like.

-

This is not meant to be a comprehensive Python tutorial, just something to whet your appetite.

-
-

Run this code from your browser!

-

Of course you can simply read through these examples, but it’s more fun to run them yourself:

-
    -
  • Find the “Rocket Ship” icon, located near the top-right of this page. Hover over this icon to see the drop-down menu.

  • -
  • Click the Binder link from the drop-down menu.

  • -
  • This page will open up as a Jupyter notebook in a working Python environment in the cloud.

  • -
  • Press Shift+Enter to execute each code cell

  • -
  • Feel free to make changes and play around!

  • -
-
-
-

A very first Python program

-

A Python program can be a single line:

-
-
-
print("Hello interweb")
-
-
-
-
-
Hello interweb
-
-
-
-
-
-
-

Loops in Python

-

Let’s start by making a for loop with some formatted output:

-
-
-
for n in range(3):
-    print(f"Hello interweb, this is iteration number {n}")
-
-
-
-
-
Hello interweb, this is iteration number 0
-Hello interweb, this is iteration number 1
-Hello interweb, this is iteration number 2
-
-
-
-
-

A few things to note:

-
    -
  • Python defaults to counting from 0 (like C) rather than from 1 (like Fortran).

  • -
  • Function calls in Python always use parentheses: print()

  • -
  • The colon : denotes the beginning of a definition (here of the repeated code under the for loop).

  • -
  • Code blocks are identified through indentations.

  • -
-

To emphasize this last point, here is an example with a two-line repeated block:

-
-
-
for n in range(3):
-    print("Hello interweb!")
-    print(f"This is iteration number {n}.")
-print('And now we are done.')
-
-
-
-
-
Hello interweb!
-This is iteration number 0.
-Hello interweb!
-This is iteration number 1.
-Hello interweb!
-This is iteration number 2.
-And now we are done.
-
-
-
-
-
-
-

Basic flow control

-

Like most languages, Python has an if statement for logical decisions:

-
-
-
if n > 2:
-    print("n is greater than 2!")
-else:
-    print("n is not greater than 2!")
-
-
-
-
-
n is not greater than 2!
-
-
-
-
-

Python also defines the True and False logical constants:

-
-
-
n > 2
-
-
-
-
-
False
-
-
-
-
-

There’s also a while statement for conditional looping:

-
-
-
m = 0
-while m < 3:
-    print(f"This is iteration number {m}.")
-    m += 1
-print(m < 3)
-
-
-
-
-
This is iteration number 0.
-This is iteration number 1.
-This is iteration number 2.
-False
-
-
-
-
-
-
-

Basic Python data types

-

Python is a very flexible language, and many advanced data types are introduced through packages (more on this below). But some of the basic types include:

-
-

Integers (int)

-

The number m above is a good example. We can use the built-in function type() to inspect what we’ve got in memory:

-
-
-
print(type(m))
-
-
-
-
-
<class 'int'>
-
-
-
-
-
-
-

Floating point numbers (float)

-

Floats can be entered in decimal notation:

-
-
-
print(type(0.1))
-
-
-
-
-
<class 'float'>
-
-
-
-
-

or in scientific notation:

-
-
-
print(type(4e7))
-
-
-
-
-
<class 'float'>
-
-
-
-
-

where 4e7 is the Pythonic representation of the number \( 4 \times 10^7 \).

-
-
-

Character strings (str)

-

You can use either single quotes '' or double quotes " " to denote a string:

-
-
-
print(type("orange"))
-
-
-
-
-
<class 'str'>
-
-
-
-
-
-
-
print(type('orange'))
-
-
-
-
-
<class 'str'>
-
-
-
-
-
-
-

Lists

-

A list is an ordered container of objects denoted by square brackets:

-
-
-
mylist = [0, 1, 1, 2, 3, 5, 8]
-
-
-
-
-

Lists are useful for lots of reasons including iteration:

-
-
-
for number in mylist:
-    print(number)
-
-
-
-
-
0
-1
-1
-2
-3
-5
-8
-
-
-
-
-

Lists do not have to contain all identical types:

-
-
-
myweirdlist = [0, 1, 1, "apple", 4e7]
-for item in myweirdlist:
-    print(type(item))
-
-
-
-
-
<class 'int'>
-<class 'int'>
-<class 'int'>
-<class 'str'>
-<class 'float'>
-
-
-
-
-

This list contains a mix of int (integer), float (floating point number), and str (character string).

-

Because a list is ordered, we can access items by integer index:

-
-
-
myweirdlist[3]
-
-
-
-
-
'apple'
-
-
-
-
-

remembering that we start counting from zero!

-

Python also allows lists to be created dynamically through list comprehension like this:

-
-
-
squares = [i**2 for i in range(11)]
-squares
-
-
-
-
-
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
-
-
-
-
-
-
-

Dictionaries (dict)

-

A dictionary is a collection of labeled objects. Python uses curly braces {} to create dictionaries:

-
-
-
mypet = {
-    "name": "Fluffy",
-    "species": "cat",
-    "age": 4,
-}
-type(mypet)
-
-
-
-
-
dict
-
-
-
-
-

We can then access items in the dictionary by label using square brackets:

-
-
-
mypet["species"]
-
-
-
-
-
'cat'
-
-
-
-
-

We can iterate through the keys (or labels) of a dict:

-
-
-
for key in mypet:
-    print("The key is:", key)
-    print("The value is:", mypet[key])
-
-
-
-
-
The key is: name
-The value is: Fluffy
-The key is: species
-The value is: cat
-The key is: age
-The value is: 4
-
-
-
-
-
-
-
-

Arrays of numbers with NumPy

-

The vast majority of scientific Python code makes use of packages that extend the base language in many useful ways.

-

Almost all scientific computing requires ordered arrays of numbers, and fast methods for manipulating them. That’s what NumPy does in the Python world.

-

Using any package requires an import statement, and (optionally) a nickname to be used locally, denoted by the keyword as:

-
-
-
import numpy as np
-
-
-
-
-

Now all our calls to numpy functions will be preceeded by np.

-

Create a linearly space array of numbers:

-
-
-
# linspace() takes 3 arguments: start, end, total number of points
-numbers = np.linspace(0.0, 1.0, 11)
-numbers
-
-
-
-
-
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
-
-
-
-
-

We’ve just created a new type of object defined by NumPy:

-
-
-
type(numbers)
-
-
-
-
-
numpy.ndarray
-
-
-
-
-

Do some arithmetic on that array:

-
-
-
numbers + 1
-
-
-
-
-
array([1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2. ])
-
-
-
-
-

Sum up all the numbers:

-
-
-
np.sum(numbers)
-
-
-
-
-
5.500000000000001
-
-
-
-
-
-
-

Some basic graphics with Matplotlib

-

Matplotlib is the standard package for producing publication-quality graphics, and works hand-in-hand with NumPy arrays.

-

We usually use the pyplot submodule for day-to-day plotting commands:

-
-
-
import matplotlib.pyplot as plt
-
-
-
-
-

Define some data and make a line plot:

-
-
-
theta = np.linspace(0.0, 360.0)
-sintheta = np.sin(np.deg2rad(theta))
-
-plt.plot(theta, sintheta, label='y = sin(x)', color='purple')
-plt.grid()
-plt.legend()
-plt.xlabel('Degrees')
-plt.title('Our first Pythonic plot', fontsize=14)
-
-
-
-
-
Text(0.5, 1.0, 'Our first Pythonic plot')
-
-
-../_images/519241a2201943d27209536f036ed151b63a528bca69610a530330d6b1ecc5b5.png -
-
-
-
-

What now?

-

That was a whirlwind tour of some basic Python usage.

-

Read on for more details on how to install and run Python and necessary packages on your own laptop.

-
-
-

Resources and references

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/terminal.html b/_preview/419/foundations/terminal.html deleted file mode 100644 index 0d8214283..000000000 --- a/_preview/419/foundations/terminal.html +++ /dev/null @@ -1,998 +0,0 @@ - - - - - - - - Python in the Terminal — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Python in the Terminal

-
-
-

Overview

-

You’d like to learn to run Python in the terminal. Here we will cover:

-
    -
  1. Installing Python in the terminal

  2. -
  3. Running Python code in the terminal

  4. -
-
-
-

Prerequisites

- - - - - - - - - - - - - -

Concepts

Importance

Notes

Installing and Running Python

Helpful

-
    -
  • Time to learn: 20 minutes

  • -
-
-
-
-

Installing Python in the Terminal

-

If you are running Python in the terminal, it is best to install Miniconda. You can do that by following the instructions for your machine.

-

Learn more about Conda here

-

Then create a Conda environment with Python installed by typing the following into your terminal:

-
$ conda create --name pythia_foundations_env python
-
-
-

You can test this by running python in the command line.

-
-
-

Running Python in the Terminal

-

On Windows, open Anaconda Prompt. On a Mac or Linux machine, simply open Terminal.

-
    -
  1. Activate your Conda environment:

    -
    $ conda activate pythia_foundations_env
    -
    -
    -
  2. -
  3. Create a directory to store our work. Let’s call it pythia-foundations.

    -
    $ mkdir pythia-foundations
    -
    -
    -
  4. -
  5. Go into the directory:

    -
    $ cd pythia-foundations
    -
    -
    -
  6. -
  7. Create a new Python file:

    -
    $ touch mysci.py
    -
    -
    -
  8. -
  9. And now that you’ve set up our workspace, edit the mysci.py script using your favorite text editor (e.g., nano):

    -
    $ nano mysci.py
    -
    -
    -
  10. -
  11. Change the script to include the classic first command: printing, “Hello, world!”.

    -
    print("Hello, world!")
    -
    -
    -
  12. -
  13. Save your file and exit the editor. How to do this is dependent on your chosen text editor.

    -
      -
    • In Vim, revert to command mode by pressing esc. Then, the command is :wq.

    • -
    • In Nano it is Ctrl+O to save and Ctrl+X to exit (where you will be prompted if you want to save it, if modified).

    • -
    -
  14. -
  15. In the terminal, execute your script:

    -
    $ python mysci.py
    -
    -
    -
  16. -
-

Congratulations! You have just set up your first Python environment and run your first Python script in the terminal.

-
-
-
-

Summary

-

Running Python in the terminal is a good option if you are familiar with Linux commands or scripting on a supercomputer. It requires the use of a text editor.

- -
-
-

Resources and References

- -
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/foundations/why-python.html b/_preview/419/foundations/why-python.html deleted file mode 100644 index cd007bd90..000000000 --- a/_preview/419/foundations/why-python.html +++ /dev/null @@ -1,896 +0,0 @@ - - - - - - - - Why Python? — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Why Python?

-

You’re already here because you want to learn to use Python for your data analysis and visualizations.

-

Perhaps the #1 reason to use Python is because it is so widely used in the scientific community!

-

Python can be compared to other high-level, interpreted, object-oriented languages, but is especially great because it is free and open source!

-

Want to know what these terms mean for you and your work? Read on!

-
-

High level languages

-

Other high level languages include MatLab, IDL, and NCL. The advantage of high level languages is that they provide built-in functions, data structures, and other utilities that are commonly used, which means it takes less code to get real work done. The disadvantage of high level languages is that they tend to obscure the low level aspects of the machine such as memory use, how many floating point operations are happening, and other information related to performance. C, C++, and Fortran are all examples of lower level languages. The “higher” the level of language, the more computing fundamentals are abstracted.

-
-
-

Interpreted languages

-

Most of your work is probably already in interpreted languages if you’ve ever used IDL, NCL, or MatLab (interpreted languages are typically also high level). So you are already familiar with the advantages of this: you don’t have to worry about compiling or machine compatibility (it is portable). And you are probably familiar with their deficiencies: sometimes they can be slower than compiled languages and potentially more memory intensive.

-
-
-

Object Oriented languages

-

Objects are custom datatypes. For every custom datatype, you usually have a set of operations you might want to conduct. For example, if you have an object that is a list of numbers, you might want to apply a mathematical operation, such as sum, onto this list object in bulk. Not every function can be applied to every datatype; it wouldn’t make sense to apply a logarithm to a string of letters or to capitalize a list of numbers. Data and the operations applied to them are grouped together into one object.

-
-
-

Open source

-

Python as a language is open source, which means that there is a community of developers behind its codebase. Anyone can join the developer community and contribute to deciding the future of the language. When someone identifies gaps in Python’s abilities, they can write up the code to fill these gaps. The open source nature of Python means that Python as a language is very adaptable to the shifting needs of the user community. This harkens back to the idea that the widespread use of Python within the scientific community is a benefit to you! The large Python user base within your field has established high level community Python packages that are available to you in your workflow.

-

Python is a language designed for rapid prototyping and efficient programming. It is easy to write new code quickly with less typing.

-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/genindex.html b/_preview/419/genindex.html deleted file mode 100644 index b669cfe22..000000000 --- a/_preview/419/genindex.html +++ /dev/null @@ -1,796 +0,0 @@ - - - - - - - - Index — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- - -

Index

- -
- -
- - -
- -
-
-
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/index.html b/_preview/419/index.html deleted file mode 100644 index 99d67cfd3..000000000 --- a/_preview/419/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/_preview/419/landing-page.html b/_preview/419/landing-page.html deleted file mode 100644 index 6204f02be..000000000 --- a/_preview/419/landing-page.html +++ /dev/null @@ -1,867 +0,0 @@ - - - - - - - - Pythia Foundations — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

Pythia Foundations

-
-

A community learning resource for Python-based computing in the geosciences

-Pretty Earth -

This collection covers the foundational skills everyone needs to get started with scientific computing in the open-source Python ecosystem.

-

Project Pythia logo Brought to you by Project Pythia, the education working group for Pangeo Pangeo logo

-

DOI

-
-
-
-
-
-
-
-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/objects.inv b/_preview/419/objects.inv deleted file mode 100644 index 2cab0c8cc..000000000 Binary files a/_preview/419/objects.inv and /dev/null differ diff --git a/_preview/419/preamble/how-to-cite.html b/_preview/419/preamble/how-to-cite.html deleted file mode 100644 index 8b0df660f..000000000 --- a/_preview/419/preamble/how-to-cite.html +++ /dev/null @@ -1,854 +0,0 @@ - - - - - - - - How to Cite This Book — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -
-

How to Cite This Book

-

The material in Pythia Foundations is licensed for free and open consumption and reuse. All code is served under Apache 2.0, while all non-code content is licensed under Creative Commons BY 4.0 (CC BY 4.0). Effectively, this means you are free to share and adapt this material so long as you give appropriate credit to the Project Pythia community.

-

The source code for the book is released on GitHub and archived on Zenodo. This DOI will always resolve to the latest version of the book source: -https://doi.org/10.5281/zenodo.7884571

-

If material in Pythia Foundations is useful in published work, you can cite a specific version of the book as:

-
-

Rose, B. E. J., Kent, J., Tyle, K., Clyne, J., Banihirwe, A., Camron, D., May, R., Grover, M., Ford, R. R., Paul, K., Morley, J., Eroglu, O., Kailyn, L., & Zacharias, A. (2023). Pythia Foundations (Version v2023.05.01) https://doi.org/10.5281/zenodo.7884572

-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/preamble/how-to-use.html b/_preview/419/preamble/how-to-use.html deleted file mode 100644 index 41f4c0501..000000000 --- a/_preview/419/preamble/how-to-use.html +++ /dev/null @@ -1,1004 +0,0 @@ - - - - - - - - How to Use This Book — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - - - -
- - - -
-
-
-
- -
- -
-

How to Use This Book

-
-

Preamble

-

Pythia Foundations is intended to educate the reader on the essentials -for using the Scientific Python Ecosystem (SPE): a collection of -open source Python packages that support analysis, manipulation, -and visualization of scientific data. While Project Pythia is focused -on the geoscience communities, the material contained in Pythia -Foundations applies broadly, and may be useful to scientists, or -aspiring scientists, working in any number of disciplines. Pythia -Foundations makes very few assumptions about the experience level -of the reader other than having a background in math or science, -and being comfortable using a computer, including the command line -terminal (i.e. the Unix shell). -Prior programming experience is not required.

-

Lastly, in addition to the Python language and a number of fundamental -scientific Python packages, Pythia Foundations covers two topics -that we believe are essential -to effectively using the SPE: GitHub, for sharing workflows and -managing code; and Jupyter Notebooks, the technology by which all -Pythia Foundations material is presented and which is quickly -becoming a standard format for scientific communication of computational -results.

-
-
-

Organization of Pythia Foundations

-

Pythia Foundations is organized into two main sections as seen in -the sidebar on the left: Foundational Skills, and Core Scientific -Python Packages. The first, Foundational Skills, covers essential -material that all users of Project Pythia are expected to feel -comfortable with in order to make the most of the rest of the Project -Pythia content. The second, Core Scientific Python Packages, covers -what we believe are the most important and fundamental packages -in the Scientific Python Ecosystem. These packages serve as the -building blocks for many of the more geoscience focused components -of the Scientific Python Ecosystem.

-
-
-

Running Pythia Foundations examples

-

All of the content in Pythia Foundations is authored in Markdown -and presented in the form of Jupyter -Notebook “chapters”. The power -of Jupyter Notebooks is that they can contain both static text and -executable code that you can interact with. When you navigate to a -book chapter such as Matplotlib -Basics -you will see static text, Python code, and the rendered output of -that code in the form of the many figures that appear. In the case -of Matplotlib Basics these figures are produced by Matplotlib itself. -Viewing content in this manner is not much different than reading -a hardbound textbook. To get the full benefit of Jupyter Books you -can run, and even modify, the example code in real time! This -interaction allows you to experiment with different parameters and -observe instantly how results change.

-

There are two ways that you can execute the Pythia Foundations book -chapters. Both are described below.

-
-

Interacting with Jupyter Notebooks in the cloud via Binder

-

The simplest way to interact with a Jupyter Notebook is through -Binder, which enables the execution of a -Jupyter Notebook in the cloud. The details of how this works are not -important for now. All you need to know is how to launch a Pythia -Foundations book chapter via Binder. Simply navigate your mouse to -the top right corner of the book chapter you are viewing and click -on the rocket ship icon, (see figure below), and be sure to select -“Launch Binder”. After a moment you should be presented with a -notebook that you can interact with. You’ll be able to execute code -and even change the example programs. At first the code cells -have no output, until you execute them by pressing -Shift+Enter. Complete details on how to interact with -a live Jupyter notebook are described in Getting Started with -Jupyter.

-
-
-

Interacting with Jupyter Books locally

-

Sometimes it may make more sense to download a book chapter and run -it on your local laptop or PC. Perhaps you want to co-opt a book -for your own purposes, or load your own local data. Downloading an -individual chapter is trivial: click on the download icon, also -located in the top right corner of the book chapter you are viewing, -and select “.ipynb”.

-

That was the easy part. Getting the notebook to execute locally may -take a little more work. The book was created to run in a particular -Python environment, managed with Conda. If you don’t have a up-to-date -version of Conda on your machine, you’ll want to install one. A brief -introduction to installing Conda is available here.

-

Once you’ve installed Conda you will need to create and activate a Conda environment -that is compatible with Pythia Foundation’s notebooks. This -can be done with two commands from the terminal, one to create the -environment and one to activate it:

-
conda env create --force -f https://raw.githubusercontent.com/ProjectPythia/pythia-foundations/main/environment.yml
-conda activate pythia-book-dev
-
-
-

You should only need to create the environment once (run the first -command above). But if you download another notebook later, you will -need to activate pythia-book-dev if -it is not currently active, for example if you open up a new -terminal window, or deactivate pythia-book-dev explicitly with -the conda command. Again, more information on Conda can be -found here.

-

Now that your pythia-book-dev environment is activated, -change your working directory to the -location where you downloaded the notebook (.ipynb file) and start -the Jupyter Notebook server. For example if you downloaded the -notebook file to your home directory you would do:

-
cd ~
-jupyter lab
-
-
-

A local Jupyter Notebook server should open in your web browser. -Simply open the .ipynb file using the Notebook server’s file browser -and you are good to go. If you want to work with many Pythia Foundations -notebooks, you might want to “clone the site” -and download all of the notebooks. First click on the Pythia -Foundations GitHub icon (see figure below) and select repository. -Then follow the instructions in our Getting Started with GitHub -guide -for cloning a repository. The steps used above for configuring your -Conda environment should work for this method as well.

-

Annotated Pythia Foundations home page

-
-
-
- - - - -
- - -
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/search.html b/_preview/419/search.html deleted file mode 100644 index 90203da7b..000000000 --- a/_preview/419/search.html +++ /dev/null @@ -1,825 +0,0 @@ - - - - - - - - Search — Pythia Foundations - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - -
-
- -
- - - - - - - - - - - - - - -
- - -
- -
-
-
-
-
- -
- -

Search

- - - - -

- Searching for multiple words only shows matches that contain - all words. -

- - -
- - - -
- - - -
- -
- - -
- -
-
-
-
-
- -
-
- - - - - - \ No newline at end of file diff --git a/_preview/419/searchindex.js b/_preview/419/searchindex.js deleted file mode 100644 index 38d414e0a..000000000 --- a/_preview/419/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["appendix/how-to-contribute","appendix/template","core/cartopy","core/cartopy/cartopy","core/data-formats","core/data-formats/netcdf-cf","core/datetime","core/datetime/datetime","core/matplotlib","core/matplotlib/annotations-colorbars-layouts","core/matplotlib/histograms-piecharts-animation","core/matplotlib/matplotlib-basics","core/numpy","core/numpy/intermediate-numpy","core/numpy/numpy-basics","core/numpy/numpy-broadcasting","core/overview","core/pandas","core/pandas/pandas","core/xarray","core/xarray/computation-masking","core/xarray/dask-arrays-xarray","core/xarray/enso-xarray","core/xarray/xarray-intro","foundations/conda","foundations/getting-started-github","foundations/getting-started-jupyter","foundations/getting-started-python","foundations/github/basic-git","foundations/github/contribute-to-pythia","foundations/github/git-branches","foundations/github/github-advanced","foundations/github/github-cloning-forking","foundations/github/github-issues","foundations/github/github-pull-request","foundations/github/github-repos","foundations/github/github-setup-advanced","foundations/github/github-workflows","foundations/github/review-pr","foundations/github/what-is-github","foundations/how-to-run-python","foundations/jupyter","foundations/jupyterlab","foundations/markdown","foundations/overview","foundations/quickstart","foundations/terminal","foundations/why-python","landing-page","preamble/how-to-cite","preamble/how-to-use"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":5,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinxcontrib.bibtex":9,sphinx:56},filenames:["appendix/how-to-contribute.md","appendix/template.ipynb","core/cartopy.md","core/cartopy/cartopy.ipynb","core/data-formats.md","core/data-formats/netcdf-cf.ipynb","core/datetime.md","core/datetime/datetime.ipynb","core/matplotlib.md","core/matplotlib/annotations-colorbars-layouts.ipynb","core/matplotlib/histograms-piecharts-animation.ipynb","core/matplotlib/matplotlib-basics.ipynb","core/numpy.md","core/numpy/intermediate-numpy.ipynb","core/numpy/numpy-basics.ipynb","core/numpy/numpy-broadcasting.ipynb","core/overview.md","core/pandas.md","core/pandas/pandas.ipynb","core/xarray.md","core/xarray/computation-masking.ipynb","core/xarray/dask-arrays-xarray.ipynb","core/xarray/enso-xarray.ipynb","core/xarray/xarray-intro.ipynb","foundations/conda.md","foundations/getting-started-github.md","foundations/getting-started-jupyter.ipynb","foundations/getting-started-python.md","foundations/github/basic-git.md","foundations/github/contribute-to-pythia.md","foundations/github/git-branches.md","foundations/github/github-advanced.md","foundations/github/github-cloning-forking.md","foundations/github/github-issues.md","foundations/github/github-pull-request.md","foundations/github/github-repos.md","foundations/github/github-setup-advanced.md","foundations/github/github-workflows.md","foundations/github/review-pr.md","foundations/github/what-is-github.md","foundations/how-to-run-python.md","foundations/jupyter.md","foundations/jupyterlab.ipynb","foundations/markdown.md","foundations/overview.md","foundations/quickstart.ipynb","foundations/terminal.md","foundations/why-python.md","landing-page.md","preamble/how-to-cite.md","preamble/how-to-use.md"],objects:{},objnames:{},objtypes:{},terms:{"0":[3,5,7,9,10,11,13,14,15,18,20,21,22,23,24,32,42,45,49],"00":[5,7,14,18,20,21,22,23],"000":21,"000000":18,"000000000":23,"00000000e":14,"000003814697266earth_radiu":23,"000003814697266longitude_of_central_meridian":23,"00000762939453standard_parallel":23,"0001":[20,22],"000744e":23,"0007539":20,"0007784":20,"000791":20,"0008452":20,"000896":20,"000933":20,"001":21,"00130295753479":7,"001e":23,"00218808":23,"002e":23,"003":[20,21,22],"00325119":21,"003e":23,"00449019":21,"005718":5,"00595246":21,"006453e":23,"0078":23,"0078125":23,"007e":23,"00807236":21,"008482":22,"008e":23,"009292":13,"0099249":15,"00axi":20,"00parent_variant_label":[20,22],"00xarrai":20,"01":[5,7,9,14,18,20,21,22,23,49],"01012011":15,"01030226":21,"01044269":21,"01070435":21,"011contact":[20,22],"01276068":13,"012806e":23,"01307774":20,"01314461":20,"01320827":20,"01326215":20,"01326arrai":20,"01331":20,"0133127":20,"01336":20,"0133611":20,"01341":20,"01345":20,"013e":23,"01401365":20,"01401687":20,"01402068":20,"01402271":20,"01403356":20,"01404917":20,"01406252":20,"014107":22,"01412642":20,"0142287":20,"01432824":20,"01442552":20,"0145216":20,"01476014":20,"01491845":20,"015000":18,"01506364":20,"01520228":20,"0153321":20,"01544082":20,"01544118":20,"01544321":20,"01544476":20,"0154475":20,"01545036":20,"01545238":20,"01635301":20,"01636147":20,"01637137":20,"01637557":21,"01638114":20,"01639009":20,"01639998":20,"01680517":20,"01687253":20,"01693714":20,"0169601":20,"01698041":20,"01699162":20,"0170424":20,"01709175":20,"0171173":20,"01713431":20,"01719594":20,"0172386":20,"01727939":20,"0172919":20,"0173173":20,"0173297":20,"0173445":20,"0173862":20,"01742125":20,"01748013":20,"01755834":20,"01757395":20,"01766813":20,"0176847":20,"01865":22,"019159e":23,"01951141":21,"01t00":23,"02":[5,7,18,20,21,22,23],"0229578":23,"02331465":21,"02453499":23,"025":[9,11],"02690344":23,"029993":22,"02arrai":23,"02t00":23,"02t04":[20,22],"03":[7,16,18,20,21,22,23],"030000":18,"031322":22,"03176496":23,"03276029":15,"03334709":23,"03448276":14,"034894":18,"03576453":13,"03580664":23,"03600639":15,"03811793":13,"038165":20,"0383828":20,"0386322":20,"0388296":20,"038916e":23,"0389647":20,"038e":20,"0390673":20,"039428":18,"039894":23,"03arrai":23,"03standard_nam":23,"03t00":23,"03unit":23,"03xarrai":23,"04":[7,16,18,20,21,22,23],"042742":22,"0429":23,"042e":20,"043zgeospatial_lat_min":23,"045269e":23,"04641359":23,"04687463":23,"04911464":21,"0494263":20,"0494475":20,"0494578":20,"0495107":20,"0495603":20,"049596":20,"04971994":23,"04e":20,"04t00":23,"04t23":23,"05":[9,14,18,20,21,22,23,49],"0500":28,"051622e":23,"051643":22,"05282372":13,"052e":23,"0531872":15,"053625":22,"057854":22,"05877238":23,"05937297":15,"059557":22,"059725":18,"05984232":13,"05arrai":[20,23],"05t00":23,"06":[5,7,18,20,21,22],"060708":23,"061314":22,"063004":18,"063814":18,"0642793":20,"0644639":20,"0646174":20,"064733":20,"0648024":20,"0648358":20,"065e":20,"066883":22,"067299":22,"06896552":14,"06954335":13,"07":[5,7,16,18,20,21,22,23],"070000":18,"07098533":23,"071":23,"07106781e":14,"071379e":23,"07199963":13,"07276144":23,"073":23,"07335944":23,"0750863":21,"07572989":13,"07581677":21,"07617983":21,"07629059":13,"07677966101695":18,"076780":18,"077732e":23,"077e":20,"078":23,"08":[5,7,18,20,21,22],"081787109375":23,"0832636":20,"0834875":20,"0837412":20,"0839543":20,"08398":23,"084085e":23,"0841148":20,"0842566":20,"084e":23,"085205078125":23,"0859375":23,"086039":22,"08698248":21,"08790402":23,"088e":20,"08982828":21,"09":[7,18,20,21,22,28],"090641e":23,"0909484":23,"091969":23,"09308323":15,"09328165":21,"094604":22,"09533591":13,"09552107":13,"09561932":21,"09599544":13,"09629477":23,"0_coordinatetransformtyp":23,"0arrai":23,"0branch_time_in_par":[20,22],"0case_id":[20,22],"0long_nam":22,"0x7f467ee4e110":9,"0x7f50cbdd7d90":3,"0x7f50cbead090":3,"0x7fa9b8ce6c50":23,"0x7fa9b8d6bdf0":23,"0x7fa9b8dfb850":23,"0xarrai":23,"1":[3,5,7,9,11,13,14,15,18,20,21,22,23,31,34,37,42,45,47],"10":[1,3,5,7,9,11,13,14,15,18,20,21,22,23,26,42,45,49],"100":[5,10,13,14,15,21,22,23,32,45],"1000":[5,9,11,15,21,23,42],"100000":18,"1001":23,"1002":23,"100246":22,"1003":23,"1004":23,"1005":23,"1006":23,"100644":[28,37],"1007":23,"1008":23,"100_coordinateaxistyp":23,"100grib1_level_desc":23,"100x50":13,"101":[5,13,21],"1011":23,"1012":23,"1013":23,"10191176":23,"102":[11,13,23],"10271":22,"10344828":14,"103842e":23,"104733":22,"105":[5,11,23],"10549575":13,"105arrai":23,"10635524":14,"1065311":21,"107":[21,23],"1078214":23,"108":11,"109":23,"10933":23,"10cell_method":22,"10lon":22,"10m":3,"11":[3,7,13,14,15,18,20,21,22,23,28,45],"110":[11,23],"110195e":23,"110m":3,"111":11,"11344716":13,"11354245":21,"114":11,"115000":18,"116548e":23,"11664000":[20,22],"116751e":23,"117":[11,21],"1175743":23,"117e":23,"11828592":23,"118896":22,"11899054":21,"119":23,"119303":22,"11976794":13,"119x":23,"11_l100":23,"11_l100grib1_cent":23,"11grib1_level_typ":23,"11realm":[20,22],"12":[5,7,13,14,15,18,20,21,22,23],"120":[11,22,23],"12001299":21,"120w":22,"1214556":23,"123":11,"1231055":23,"12323400e":14,"125":[14,23],"12531582":23,"12564928":21,"126":11,"12740693":21,"129":11,"12912925":21,"1294351":23,"12986349":23,"12arrai":20,"12lat":20,"12xarrai":20,"13":[5,7,13,14,15,18,20,21,22,23,28],"130":11,"13037":23,"131":23,"1318906":23,"131generating_process_or_model":23,"131grib1_paramet":23,"132":11,"1341411":13,"13485627":23,"135":[11,14],"136305e":23,"136517":22,"1369238":23,"137":15,"137907":22,"13793103":14,"13793765":21,"138":11,"13standard_nam":23,"13t00":23,"14":[7,13,15,18,20,21,22,23,28,32,45],"140":3,"140000":18,"140859e":23,"141":11,"14100":[20,22],"1415926":7,"14159265":14,"141592653589793":14,"14164393":23,"14243191":21,"142658e":23,"142861e":23,"1435589":13,"144":[11,33],"145":33,"1450":23,"1450km":23,"14675698":23,"147":[11,18],"1476":14,"14769459":21,"14818369":15,"14823932":21,"14999155":21,"15":[3,5,7,9,10,13,14,15,18,20,21,22,23,28,32,35,39],"150":[5,11,23],"150000":18,"150390625":23,"1504":23,"15175649":13,"15188778":21,"15243641":21,"152500":18,"153":[5,11,23],"15576171875":23,"15578434":21,"156":[11,33],"157152":22,"157590":18,"15772":22,"158524":22,"158993":22,"159":11,"1591796875":23,"15939013":21,"15945525":21,"1599731445312":23,"15grib1_tablevers":23,"15lat":20,"16":[5,7,9,11,13,14,15,18,20,21,22,23,45],"160000":18,"16062611":15,"160896":22,"162":11,"16652571":13,"16666667":15,"166970e":23,"16816435":21,"168768e":23,"168971e":23,"1691536741":7,"16h":7,"17":[5,7,13,14,15,18,20,21,22,23,24,28],"170w":22,"17158":22,"17241379":14,"17333452":21,"17388164":23,"17403196":15,"17453499":23,"175":[15,23],"175121e":23,"17565215":23,"176":18,"176163":22,"17773618":13,"17856":22,"18":[5,7,9,13,15,18,20,21,22,23],"180":[3,14,20,21,22],"18042929":21,"1806":20,"180d2":[20,22],"180dask":21,"180lat":[20,21],"180lon":[20,21,22],"180x360":[20,22],"181":3,"18176496":23,"18181589":23,"18183079":21,"182":23,"18216":23,"182km":23,"183":21,"18312259":23,"184":7,"18435229":21,"1850":[20,22],"1851116":21,"1876712":20,"1879027":20,"1881146":20,"1883302":20,"18839824":21,"1885312":20,"188714":20,"18m":7,"19":[5,7,13,15,18,20,21,22],"190":[20,21,22],"190000":18,"191":22,"192":[20,22],"193":22,"193079e":23,"194":22,"195":22,"1950":22,"195081e":23,"19572063":13,"195834":23,"196":22,"19669751":21,"19683":14,"197":22,"1970":7,"1979":22,"19793004":13,"198":22,"1982":18,"1983":18,"1984":18,"1985":18,"1986":18,"1987":18,"1988":18,"1989":18,"199":22,"1990":18,"1991":18,"1992":18,"1993":[18,23],"1994":18,"1995":18,"1996":18,"1997":18,"1998":18,"1999":18,"19990503":13,"19pm":7,"1d":10,"1d7b":[20,22],"1descript":23,"1e":[20,21,22,23],"1f":10,"1frequenc":[20,22],"1institut":[20,22],"1isobaric1":23,"1product":[20,22],"1standard_nam":23,"1x1":[11,20,21,22],"1y":[18,23],"2":[3,5,7,9,11,13,14,15,18,20,21,22,30,31,32,34,37,42,45,49],"20":[10,13,14,15,16,18,20,21,22,24,40,41,46],"200":[9,10,21,22,23],"2000":[15,18,20,21,22,28],"2001":[18,20,21,22],"2002":[18,20,21,22],"2003":[18,20,21,22],"2004":[18,20,21,22],"2005":[16,18,20,21,22],"2006":[18,20,21,22],"2007":[7,18,20,21,22],"20073714":23,"2008":[18,20,21,22],"2009":[18,20,21,22],"201":[20,22],"2010":[18,20,21,22],"2011":[18,20,21,22],"20116345":21,"2012":[18,20,21,22],"201231e":23,"2013":[18,20,21,22],"2014":[18,20,21,22],"2015":[16,18,22],"2016":[7,18],"2017":[18,20,22],"2018":[18,23],"2019":[5,18,20,22],"202":[21,22],"2020":18,"2021":[7,18],"2022":28,"2023":[5,7,49],"20258584":21,"203":22,"2032912":20,"203617":20,"20368424":21,"203904":20,"204":22,"2041442":20,"2043478":20,"2045207":20,"20472628":21,"20478923":21,"205":22,"205000":18,"2055071":21,"205938":22,"206":22,"20684877":21,"20689655":14,"207":22,"207584e":23,"208":[21,22],"20811746":21,"209":22,"209619":18,"20th":[20,22],"21":[5,7,13,15,18,20,21,22,23],"210":22,"21020959":23,"211":22,"212":[15,22],"212313":22,"212325":23,"21244802":21,"213":22,"21372499":23,"214":22,"214586":22,"2147483647":5,"215":[22,23],"216":[22,23],"217":[22,23],"217098":22,"218":22,"21898602":23,"219":[22,23],"219000":[20,22],"219189e":23,"21948685":23,"22":[5,13,15,18,20,21,22,23],"220":[21,22],"2200":5,"220537":20,"22081671":23,"2209656":20,"22098533":23,"221":22,"221191e":23,"22129221":21,"2213385":20,"2216525":20,"221913":20,"222":22,"2221622":20,"223":22,"22335944":23,"224":[22,23],"2246":23,"224609375":23,"22464680e":14,"22467":23,"22476884":21,"225":[14,22,23],"226":22,"2268635":23,"227":22,"22752":22,"2276":23,"228":[22,23],"229":22,"22922395":21,"229736328125":23,"23":[5,7,13,15,18,20,21,22],"230":22,"23056443":21,"231":22,"23190551":21,"232":22,"23222462":15,"23271986e":22,"233":22,"233154296875":23,"23344556":21,"23351938":13,"233694e":23,"2339477539062":23,"233e":22,"234":[22,23],"23422552e":22,"235":22,"2352104":23,"236":22,"236293e":23,"237":22,"238":[22,23],"239":22,"24":[3,5,7,9,13,14,15,18,20,21,22],"240":[20,22],"240047e":23,"240423":22,"24073197":13,"24094826":21,"2409484":23,"24137931":14,"242":23,"242203":22,"24232998":21,"243":23,"245299e":23,"24545829":21,"246":23,"24629477":23,"247301e":23,"24892858":21,"249":23,"25":[5,7,10,11,13,14,15,18,20,21,22,23,45],"250":[5,10,15,23],"2500":10,"25007975":21,"250171":20,"25186164":13,"25255":22,"253":[21,23],"253191":22,"253344":22,"254429":23,"256025":22,"256308e":23,"25668992":13,"257":[11,23],"257241":23,"25745942":21,"25747307":21,"2578214":23,"259":23,"25arrai":20,"26":[5,7,13,14,18,20,21,22],"260":[11,23],"26070834":21,"26115286":23,"26167138":21,"262":23,"26350974":21,"264":23,"26468701":13,"265177":22,"266":23,"266157e":23,"26636":23,"2675743":23,"268":23,"26828592":23,"26875609":21,"268coordin":23,"269":23,"27":[5,7,13,14,15,16,18,20,21,22],"270":14,"270000":18,"271":[20,23],"27136132":21,"271409e":23,"2714556":23,"272":23,"27245148":21,"272510e":23,"273":[18,20,23],"273411e":23,"27350814":13,"27369636":13,"274":23,"27494489":15,"275":[14,23],"27586207":14,"276":23,"27662304":13,"277":23,"27762224":23,"278":23,"279":23,"2794351":23,"2797517":13,"28":[5,13,15,18,20,21,22],"280":23,"28041414":21,"281":23,"2818906":23,"282":23,"28214278":23,"2824":23,"283":23,"28318531":14,"28325182":15,"284":23,"285":[11,23],"2856":23,"286":23,"28667009":21,"287":[11,15,23],"288":[11,20,22,23],"2889":23,"289":[11,23],"28976391":21,"29":[5,13,18,20,21,22,23,28],"290":[11,23],"29059571":23,"291":[11,23],"29100":23,"292":11,"2921":23,"29215":23,"292e":23,"293":11,"29328477":15,"29361272":13,"295":11,"2954":23,"296":11,"297":18,"297519e":23,"2975ffd3":[20,22],"298":[18,23],"29858388":21,"2986":23,"298620e":23,"298828125":23,"299":[11,18],"299521e":23,"29experi":[20,22],"29y":23,"2activity_id":[20,22],"2lat":[20,22],"2lon":23,"2nd":[14,32],"3":[3,5,7,9,11,13,14,15,16,18,20,21,23,32,40,41,42,45],"30":[3,5,7,9,10,11,13,14,15,18,20,21,22,29,30,31,32,38],"300":[5,11,15,18,21,22,23],"300000":18,"301":[11,18],"30128494":21,"3019":23,"302":[11,18],"30280232":23,"303":11,"3032":23,"3037109375":23,"304":11,"304973e":23,"305":11,"3051":23,"306":11,"3064":23,"307":11,"30712890625":23,"3079223632812":23,"308":11,"3084":23,"309":[11,21],"3097":23,"31":[5,13,14,15,18,20,21,22,28],"310":[11,20],"31034483":14,"311":11,"3116":23,"31180935":13,"31186415":21,"311e":23,"312":11,"3129":23,"313":11,"315":[11,14],"31500913":21,"316":11,"31602671":13,"3162":23,"316456":22,"31892":23,"3194":23,"32":[5,13,15,18,20,21,22],"320":11,"32026105":23,"320x384":[20,22],"32167848":21,"32197396":15,"322500":18,"3227":23,"324e":23,"325":15,"32554905":21,"3259":23,"326":21,"32615803":23,"32744402":23,"32818876":23,"32831662":23,"3292":23,"32973988":21,"33":[5,13,15,18,20,21,22],"330000":18,"331083e":23,"3324":23,"33312259":23,"3360727":15,"337436e":23,"33756518":21,"337622":22,"33846387":21,"33916721":13,"33_l100grib1_cent":23,"33f212ea4eb2":[20,22],"33f212ea4eb2variable_id":[20,22],"33grib1_level_typ":23,"34":[5,13,14,15,18,20,21,22],"34335343":23,"343e":23,"344204":22,"34482759":14,"34603239":13,"34620461":13,"346348":22,"34904914":15,"349621":18,"34971928":13,"34_l100grib1_cent":23,"34grib1_level_typ":23,"35":[5,13,14,15,18,20,21,22,36],"350":[20,21,22,23],"351":[20,21,22],"352":[20,21,22],"353":[20,21,22],"354":[20,21,22],"35495":20,"355":[20,21,22],"3551":20,"3552":20,"3553":20,"3554":20,"35572599":21,"35584781":23,"356":[20,21,22],"35619449":14,"35638476":23,"357":[20,21,22],"35721661":23,"358":[20,21,22],"359":[20,21,22],"35957795":21,"3597851":21,"35986":20,"35992":20,"35fcbd9":28,"35fcbd991f911e170df550db58f74a082ba18b50":28,"36":[5,13,18,20,21,22,45],"360":[14,20,21,22,45],"36005":20,"36014":20,"36021141":13,"36023":20,"360coordin":[20,22],"360dask":21,"360lat":21,"360nan":20,"362":15,"363546e":23,"3643":20,"36435":20,"3644":20,"36447":20,"36453":20,"3646":20,"36487912":21,"3652":18,"3653":18,"3654":18,"3655":18,"3656":18,"36563657":15,"3657":18,"3658":18,"36581882":21,"3659":18,"3660":18,"36666667":15,"367132":22,"36743547":23,"36869563":15,"36898602":23,"36948685":23,"369899e":23,"37":[13,14,15,18,20,21,22,23],"37081671":23,"37161382":21,"372146":23,"37255859375":23,"37354":23,"375":14,"37573713":13,"37582999":21,"3759":18,"3760":18,"3761":18,"3762":18,"3763":18,"3768635":23,"37759899":13,"37931034":14,"38":[7,13,15,18,20,21,22],"380000":18,"381103515625":23,"3819580078125":23,"38452656":21,"384x320":[20,22],"38667651":21,"38953375":21,"38955":22,"39":[13,15,18,20,21,22],"39016407":15,"39335946":23,"3938":20,"39383":20,"39386":20,"39392":20,"394":20,"39407":20,"39487287":15,"396009e":23,"39646915":23,"39853293":13,"39932":20,"39935":20,"39938":20,"39944":20,"39948":20,"3dim_2":23,"3km":5,"3lat":23,"3lon":23,"3rd":[10,14,32],"3x4":14,"4":[2,3,5,7,9,11,13,14,15,18,20,21,23,28,37,42,45,49],"40":[3,5,9,10,13,15,18,20,21,22,23,28],"400":[15,21,23],"4000":21,"40123859":23,"402362e":23,"40291893":23,"403":[21,37],"4037":20,"40372":20,"40375":20,"4067":20,"40674":20,"40677":20,"40683":20,"4069":20,"40695":20,"41":[13,14,15,18,20,21,22,23,32],"41093":22,"41199159":21,"412":21,"41234131":23,"41293":20,"41296":20,"413":20,"41306":20,"41363352":21,"4137931":14,"41486298":23,"415":23,"415271e":23,"41633475":21,"41766":20,"4177":20,"41772":20,"41851673":21,"41902413":23,"41arrai":23,"42":[5,13,15,18,20,21,22,23],"421":23,"422":14,"42277612":13,"423113":22,"423643":22,"424":23,"424km":23,"425608":22,"42796381":13,"428472e":23,"4287":23,"4288":23,"43":[13,15,18,20,21,22],"430000":18,"431":[20,21,22],"431522":18,"43329229":13,"43333333":15,"43409519":21,"434825e":23,"4358763":23,"4360935":23,"437":15,"4397634":21,"44":[13,14,15,18,20,21,22],"44059571":23,"441381e":23,"44197284":21,"44379078":15,"44404568":21,"44677734375":23,"447067":22,"4474326":21,"44749909":15,"44807":23,"44827586":14,"44925685":21,"44927684":13,"44929360e":14,"45":[3,13,14,15,18,20,21,22,23,28],"450":23,"450716":22,"4516497":23,"45280232":23,"45316175":13,"454":23,"45552845":23,"45709":22,"45775806":13,"45819755":21,"45986448":23,"46":[13,18,20,21,22,23],"46048995":13,"46062284":21,"460935e":23,"46093867":21,"461096":22,"46154921":21,"46314866":21,"46576463":13,"46641423":21,"467":[18,21],"467288e":23,"46744436":23,"467491e":23,"468":[18,21],"469":18,"46945053":21,"47":[13,18,20,21,22,23,32],"470":18,"4707":23,"470703125":23,"471":18,"471653":22,"47195507":21,"472":18,"472462":23,"47384984":23,"47387517":15,"475":[14,15],"47533763":21,"47578218":21,"47615803":23,"47838793":21,"47998158":13,"47e3":[20,22],"48":[13,14,18,20,21,22],"482593e":23,"48275862":14,"48338375":23,"485212":22,"486":23,"48600649":23,"48608715":13,"48944389":21,"49":[13,18,20,21,22,28,45],"49044752":21,"49158381":21,"491600e":23,"493398e":23,"493601e":23,"49392927":21,"49497791":13,"49576423":15,"49646238":21,"49778714":14,"49905207":13,"4997":23,"499751e":23,"4arrai":20,"4bc074c":28,"4c":22,"4coordin":23,"4e7":45,"5":[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23,33,37,42,45],"50":[3,5,7,13,15,16,18,20,21,22,23,42],"500":[5,15,16,21,22,23],"500000000000001":45,"50190735":21,"502608e":23,"5050":23,"50584781":23,"50638476":23,"5065":[20,22],"50773088":21,"5083":23,"50coordin":22,"50m":3,"51":[13,14,18,20,21,22],"5107226":23,"51080706":23,"511":23,"5115":23,"512":15,"51242184":23,"512500":18,"51474577":13,"5148":23,"515":21,"515000":18,"51641586":13,"51722022":21,"51724138":14,"51729638":21,"51743547":23,"517709e":23,"51784389":13,"5180":23,"518036":22,"518108":22,"51829518":13,"519":23,"519711e":23,"5198":23,"52":[5,13,15,18,20,21,22],"52010305":21,"5213":23,"522951":22,"52313905":13,"52425061":21,"5245":23,"525861e":23,"5278":23,"5281":49,"53":[13,15,18,20,21,22],"53049864":23,"53070256":13,"5310":23,"5311":14,"53189864":21,"532214e":23,"53264733":21,"53341595":23,"5343":23,"537773":22,"54":[13,15,18,20,21,22],"54034678":13,"54119192":21,"54182445":21,"542961":22,"54331325":23,"54335946":23,"54365239":23,"543820e":23,"544677734375":23,"5447":23,"545821e":23,"54684991":23,"54718":23,"54749823":15,"54773258":21,"54800087":21,"54806322":21,"548095703125":23,"55":[13,14,15,18,20,21,22,23],"550":[15,23],"55009856":23,"551":23,"55111512e":14,"55123859":23,"55153863":23,"55159143":21,"55172414":14,"5536904":7,"554":23,"55400275":23,"55457405":21,"55585648":23,"556413":23,"55797222":15,"558324e":23,"56":[13,15,18,20,21,22,23],"560463":22,"56128856":15,"56184846":23,"56212":22,"56234131":23,"56282449":15,"56342788":21,"564677e":23,"56486298":23,"565000":18,"56902413":23,"56967086":13,"569714":22,"569930e":23,"57":[13,15,18,20,21,22,23],"570000":18,"570702":22,"57079633":14,"57192922":21,"571931e":23,"572500":18,"57368908":21,"57564834":23,"57569453":13,"57644944":15,"579597":22,"58":[13,14,18,20,21,22,28],"582062":22,"583513":22,"583574":22,"584":23,"58591346":15,"5862069":14,"587":15,"5870188":15,"5886":7,"589234":20,"58925263":21,"58zdata_specs_vers":[20,22],"59":[5,13,15,18,20,21,22],"590082":20,"59037269":13,"590507":20,"590787e":23,"59083561":21,"59167587":21,"591869":20,"591908":20,"591917":20,"592048":20,"592894":20,"593736":20,"593815":20,"594002":20,"594081":20,"594477":20,"59487082":21,"594949":20,"595119":20,"595245":20,"595638":20,"595716":20,"595841":20,"595944":20,"596039e":23,"596162":20,"596858":20,"596950":20,"597140e":23,"597189":20,"597519":20,"597807":20,"597825":20,"598041e":23,"598177":20,"598365":20,"598850":20,"598867":20,"598896":20,"598927":20,"598995":20,"599011":20,"599373":20,"599465":20,"599490":20,"599838":20,"599917":20,"599944":20,"5arrai":[21,23],"5axi":[20,21,22],"5dim_1":23,"5lat":23,"5n":22,"5s":22,"5xarrai":[20,21],"6":[5,7,11,13,14,15,18,20,21,22,23,28,37,45],"60":[13,15,18,20,21,22,23,34,37],"600":[21,23],"600000":18,"600134":20,"600215":20,"600334":20,"600490":20,"600670":20,"600784":20,"600801":20,"600808":20,"600898":20,"600970":20,"601205":20,"6012342":21,"601256":20,"601372":20,"601706":20,"601735":20,"601754":20,"602013":20,"602034":20,"602108":20,"602155":20,"602329":20,"602349":20,"602514":20,"602786":20,"602821":20,"602942":20,"603077":20,"603142":20,"6031634":21,"603380":20,"603471":20,"603527":20,"603724":20,"603727":20,"603767":20,"603790":20,"603876":20,"603930":20,"603976":20,"604046":20,"604124":22,"604144":20,"604249":20,"604386":20,"604685":20,"604733":20,"604798":20,"604814":20,"604928":20,"605144":20,"605349":20,"605376":20,"605399":20,"605488":20,"605606":20,"605644":20,"605734":20,"605750":20,"605954":20,"605970":20,"606126":20,"606152":20,"606259":20,"606303":20,"606310":20,"606512":20,"606868":20,"607196":20,"607285":20,"607379":20,"607701":20,"607702":20,"607848":20,"607942":20,"608094":20,"608588":20,"608730":20,"608737":20,"608777":20,"608906":20,"609151":20,"609220":20,"609225":20,"609250":20,"609438":20,"609509":20,"609562":20,"609645":20,"609680":20,"609764":20,"609765":20,"60986448":23,"609922":20,"60998641":21,"61":[13,18,20,21,22],"610048":20,"610105":20,"610112":20,"610190":20,"610257":20,"610375":20,"61082889":21,"610844":20,"611091":20,"611327":20,"611434":20,"611463":20,"611722":20,"611735":20,"611808":20,"611878":20,"611881":20,"611886":20,"611901":20,"612033":20,"612056":20,"612424":20,"612615":20,"612655":20,"612935":20,"613050":20,"613369":20,"613692":20,"614213":20,"614420":20,"614610":20,"614671":20,"615665":20,"615765":20,"61578583":15,"615895":20,"616":[21,23],"616295":20,"616360":20,"616563":20,"616853":20,"617450":20,"61865234375":23,"618686":20,"61902541":21,"619367":20,"619794":20,"619830":20,"619949":20,"62":[13,14,18,20,21,22],"620000":18,"62001244":15,"62068966":14,"6220703125":23,"622149e":23,"6229248046875":23,"623085":20,"623223":7,"623250e":23,"623259":7,"623508":20,"623783":20,"624151e":23,"625":[14,15],"625064":18,"62560384":15,"626036":20,"62616":22,"6263213":23,"626368":22,"627005":22,"629603e":23,"63":[13,18,20,21,22],"630000":18,"631378":7,"63387873":23,"63432657":21,"63600649":23,"6367470":23,"6371000":5,"63904801":21,"63cell_measur":22,"64":[13,18,20,21,22,45],"6416953":21,"64382108":21,"64461":22,"64522725":21,"645485":22,"646565":7,"64800":[20,22],"649":23,"64919438":23,"65":[3,13,14,18,20,21,22],"650":23,"6530609":15,"65346334":21,"65396717":21,"65410518":21,"6550261":21,"65517241":14,"655407":22,"655713e":23,"66":[13,18,20,21,22],"66017722":21,"66118":23,"66123005":23,"662":15,"662066e":23,"66309":23,"66564715":21,"666108129242815":23,"666108129242815xarrai":23,"66666667":14,"66682529":15,"66815":23,"66861453":23,"66897953":23,"66919576":21,"669386":22,"67":[5,13,18,20,21,22],"670000":18,"67105394":21,"672":20,"67224694":23,"6723493":20,"6724195":20,"6724887":20,"672563":20,"6726688":20,"6727766":20,"672arrai":20,"673963":23,"674":23,"6748274":21,"674885":[20,22],"67494217":21,"67658285":13,"67669327":23,"6771463":20,"6773272":20,"67748086":15,"677492":20,"6776317":20,"6777302":20,"6778082":20,"678553":22,"67918356":23,"67959307":21,"679721":23,"68":[13,14,18,20,21,22],"68069024":21,"681":23,"68341595":23,"68358678":23,"685179":22,"6859775":23,"686":23,"6865997":23,"68722":22,"6874666":20,"6875":23,"6876906":20,"6879102":20,"6880217":20,"688121":20,"6881752":20,"688176e":23,"6883243":20,"6883289":20,"6884427":20,"6885366":20,"6885542":20,"6886654":20,"6889572":20,"689":23,"6890831":20,"68912253":23,"6892204":20,"6893266":20,"6893964":20,"6894479":20,"6895409":20,"68965517":14,"6899008":20,"69":[13,18,20,21,22],"6902231":20,"6905178":20,"6907759":20,"6910189":20,"69162bc":37,"692626953125":23,"69363459":21,"69365239":23,"694529e":23,"69583703":13,"6962890625":23,"6969604492188":23,"6arrai":23,"6histori":23,"6unit":23,"6x6":14,"7":[5,10,11,13,14,15,18,20,21,22,23,32,45],"70":[3,13,18,20,21,22,23],"700":[5,15,23],"701273":22,"70153863":23,"702488":22,"70249478":23,"70323814":23,"70400275":23,"707":14,"70710678e":14,"707312":22,"7080":18,"70826448":13,"7088":18,"7088237":23,"709401":18,"71":[3,13,18,20,21,22],"71238898":14,"712500":18,"714":23,"71450335":21,"717":21,"71800801":21,"718281828459045":14,"71828204":13,"72":[13,14,18,20,21,22],"720":[20,22],"720639e":23,"72166924":13,"72226028":21,"722328":23,"72297653":23,"723951":22,"72413793":14,"7247376":23,"725":23,"72695544":15,"726992e":23,"727234":22,"728104":22,"72814762":13,"728893e":23,"7293599":15,"73":[5,13,18,20,21,22],"73141":21,"732275":20,"732279":20,"732295":20,"732309":20,"73507883":15,"736985":20,"737":15,"73702":20,"737043":20,"737051":20,"737165":22,"7381277":20,"7381679":20,"7382039":20,"7382454":20,"7382846":20,"73828667":13,"7383199":20,"739":7,"739901e":23,"74":[13,18,20,21,22],"74104397":21,"74220492":23,"7424251":20,"7424612":20,"7424874":20,"7425116":20,"7425283":20,"742536":20,"74444414":23,"74454872":21,"745":23,"745023":23,"7453367":13,"746":20,"7462606":20,"746261":20,"7462667":20,"746267":20,"7462697":20,"74627":20,"7462848":20,"746285":20,"746299":20,"746309":20,"7463093":20,"746756":22,"746arrai":20,"746cell_measur":20,"7471":23,"7474008":20,"7474264":20,"7474308":20,"7474365":20,"7474419":20,"7474445":20,"7476336":21,"747778":22,"748089":22,"748907e":23,"7490736":20,"749148":20,"7492163":20,"7492864":20,"7493519":20,"7494118":20,"75":[3,5,13,14,18,20,21,22],"750":23,"750508":20,"7505083":20,"750561":20,"7506049":20,"750605":20,"7506379":20,"750638":20,"7506577":20,"750658":20,"750667":20,"7506672":20,"753102e":23,"753308882144761":23,"753308882144761geospatial_lat_max":23,"7535168":20,"753602":20,"7536805":20,"7537591":20,"753833":20,"753901":20,"754":20,"7542422":20,"7543424":20,"7544289":20,"7545002":20,"754559":20,"754608":20,"754arrai":20,"755422":18,"75546285":23,"755922":20,"7559224":20,"755986":20,"7559862":20,"756058":20,"7560585":20,"756119":20,"7561191":20,"7561648":20,"756165":20,"756205":20,"7562052":20,"75862069":14,"7588665":20,"7589546":20,"7590305":20,"759095":20,"7591486":20,"7591925":20,"7593384":20,"7594113":20,"7594382":21,"759455e":23,"759478":20,"759547":20,"7596117":20,"7596704":20,"76":[13,18,20,21,22,23],"760000":18,"7602725":20,"760397":20,"7605033":20,"7605885":20,"7606541":20,"760718":20,"76171875":23,"7637281":23,"76390505":21,"76441131":15,"7646879":20,"7647004":20,"7647841":20,"7648032":20,"76483512":21,"7648666":20,"7648907":20,"7649331":20,"7649589":20,"7649865":20,"7650102":20,"7650299":20,"7650614":20,"766011e":23,"7666015625":23,"76695309":13,"76725667":23,"7690051":20,"7690798":20,"7691481":20,"7692182":20,"7692844":20,"7693441":20,"76935":23,"76947957":23,"769499":22,"76arrai":20,"77":[13,18,20,21,22],"770263671875":23,"7709350585938":23,"7709653":20,"7710832":20,"7711828":20,"7712607":20,"7713183":20,"7713748":20,"77179678":13,"77346261":21,"7743237":20,"7744204":20,"7745041":20,"7745715":20,"774626":20,"77467":20,"775":[15,23],"7763213":23,"77648":22,"77777778":14,"77789358":13,"77811451":23,"77862152":23,"77980233":13,"78":[13,18,20,21,22],"780058":22,"7805718":20,"780688":20,"780786":20,"7808627":20,"7809197":20,"7809757":20,"78108175":21,"7821493":23,"78387873":23,"78503":23,"78520609":21,"78539816":14,"78547":20,"785529":20,"785565e":23,"78559":20,"785646":20,"787766":22,"78821":22,"7884571":49,"7884572":49,"78848845":21,"789865":20,"789945":20,"79":[13,14,18,20,21,22,23],"790012":20,"790069":20,"790609":20,"79065":20,"790694":20,"790736":20,"79138474":21,"79159739":23,"791918e":23,"792121e":23,"79226746":23,"792500":18,"793085":22,"79310345":14,"79402336":21,"794727":20,"794789":20,"794837":20,"794875":20,"79548039":21,"79815935":23,"7983514":20,"7983989":20,"7984415":20,"7984871":20,"7985296":20,"7985678":20,"798arrai":20,"799":20,"79919438":23,"79952775":23,"7_l100grib1_cent":23,"7dca0292467e4bbd73643556f83fd1c52b5c113c":28,"7grib1_level_typ":23,"7grib1_subcent":23,"8":[3,5,7,9,11,13,14,15,18,20,21,22,23,37,42,45],"80":[3,13,18,20,21,22,23],"800":23,"8024155":20,"8024837":20,"8025414":20,"8025846":20,"8026177":20,"8026428":20,"80305":[20,22],"80576":23,"8082187":20,"8083031":20,"8083605":20,"8083915":20,"8083944":20,"8083988":20,"80f":13,"81":[13,18,20,21,22,45],"81113106":21,"81123005":23,"812":15,"81564456":15,"816229e":23,"818":21,"818028e":23,"818231e":23,"81861453":23,"819756":22,"82":[13,14,18,20,21,22],"82138":23,"82321":22,"824381e":23,"825":[14,23],"82559389":21,"82657311":21,"82663":23,"82669327":23,"82758621":14,"82907295":21,"83":[13,18,20,21,22],"83021476":15,"8308828962289":23,"8308828962289geospatial_lon_min":23,"83170804":21,"83358678":23,"83487":23,"83544921875":23,"83571708":13,"83593609":13,"83640061":23,"83670341":21,"83697020e":14,"83897599":23,"8390845":21,"83912253":23,"83968292":23,"84":[13,18,20,21,22],"840393":22,"84140976":21,"842339e":23,"84272747":13,"8436452":22,"84423828125":23,"844341e":23,"8449401855469":23,"844arrai":22,"84580454":13,"846294":23,"84827794":13,"849726":22,"84995":23,"85":[13,18,20,21,22],"850":[5,15,23],"850000":18,"850491e":23,"85269149":13,"85323814":23,"853286":23,"85404487":15,"85481585":14,"85494842":21,"8550875":23,"856844e":23,"85759768":21,"85926267":21,"86":[13,14,18,20,21,22],"860712":22,"86206897":14,"86652644":21,"86666667":14,"86726244":21,"868449e":23,"86895349":23,"87":[13,18,20,21,22],"870000":18,"870451e":23,"87151534":13,"87297653":23,"873777":22,"87454273":15,"874871":5,"87497222":21,"875":[14,23],"87557425":13,"87659525":23,"88":[7,13,18,20,21,22],"8807439":21,"88242040519995":23,"88242040519995geospatial_lon_max":23,"88258643":13,"882954e":23,"883371":23,"885000":18,"88523954":21,"88580664":23,"887":15,"88701":23,"889307e":23,"89":[13,14,18,20,21,22],"89220492":23,"89314169":21,"894559e":23,"89636361":15,"89655172":14,"896561e":23,"89687463":23,"897":23,"898":7,"898000":7,"89825854":13,"89825854468695":13,"89971994":23,"8x3":15,"9":[5,7,10,11,13,14,15,18,20,21,22,23,45],"90":[3,13,14,18,21],"900":23,"90000":22,"90028363":13,"903761":15,"90637799":23,"90756224":23,"90886909":15,"90966796875":23,"91":[3,13,18,21],"91122156":15,"91129404":21,"91360137":15,"9137281":23,"91390712":23,"91468564":13,"91513049":13,"915417e":23,"91663779":21,"91725667":23,"91737213":23,"9177293":23,"918253":22,"9189453125":23,"91947957":23,"91952686":15,"92":[13,18,21],"920669e":23,"921770e":23,"922":21,"92218768":14,"922671e":23,"92276144":23,"92364035":21,"92369784":21,"924868":23,"925":[5,14,15,23],"92699082":14,"92729241":23,"92809205":15,"92811451":23,"93":[11,13,14,18,21],"93083913":15,"93103448":14,"93353611":21,"93380275":13,"936568":18,"936979":23,"93888":23,"93939394":14,"94":[13,18,20,21],"940416":22,"94086729":23,"94115754":13,"94159739":23,"94226746":23,"94228197":21,"94312131":21,"94429789":21,"94476028":21,"946779e":23,"947252":22,"947880e":23,"947936":18,"94807689":15,"948781e":23,"94907865":21,"94926286":23,"94976547":21,"95":[11,13,18,21],"950":23,"950000":18,"95065202":13,"95191176":23,"952":23,"954233e":23,"95696914":13,"95713198":21,"95814392":13,"95971516":13,"96":[11,13,14,18,21],"96000763":21,"961a":[20,22],"962":15,"962500":18,"96250528":21,"96339126":21,"96413698":15,"96531466":15,"965464":18,"96551724":14,"9657":22,"969209968386869e":5,"97":[5,13,18,21,33],"97122547":15,"972":[20,22],"972139":23,"972262":23,"972arrai":23,"972cesm_casenam":[20,22],"975":23,"97505745":13,"975527769":21,"97675":23,"97711554":21,"97955":23,"97986349":23,"98":[9,13,18,21,33],"980000":18,"980343e":23,"98255242":15,"98257591":21,"9833984375":23,"98364":23,"986696e":23,"988":23,"98897599":23,"989":23,"98941212":21,"99":[11,13,18,21],"990":23,"99079356":15,"990km":23,"992":23,"99273445":23,"993":23,"99396826":21,"994":23,"99403256":13,"995":23,"99586742":13,"996":23,"99675698":23,"99683836":13,"997":23,"998":23,"999":23,"99902":23,"9999":5,"9999999999999996":14,"9arrai":23,"9h_61hxcdui":21,"9x1":[20,22],"\u211d":7,"abstract":[7,23,47],"boolean":20,"break":[1,20,21,24,28,37],"byte":[15,21],"case":[1,5,7,9,10,11,15,18,20,21,22,23,32,33,35,36,39,50],"char":5,"class":[3,5,18,23,42,45],"default":[3,5,9,10,11,14,16,18,20,28,32,33,34,35,36,41,45],"do":[0,1,5,7,9,10,11,13,14,15,16,18,22,24,25,28,30,32,33,34,37,38,39,41,42,45,46,50],"export":28,"final":[0,1,3,5,7,11,13,14,15,18,20,21,22,23,34,35,37,38,42],"float":[5,7,9,10,14,20,21,22,47],"function":[3,5,7,9,11,13,15,16,18,19,21,22,23,24,32,35,38,42,45,47],"import":[0,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,45,46,50],"int":5,"long":[7,11,14,39,42,49],"new":[5,7,9,11,13,14,15,16,18,20,21,23,24,27,29,32,33,34,35,38,39,40,41,42,44,45,46,47,50],"ni\u00f1a":22,"ni\u00f1o":20,"public":[2,11,26,33,35,37,45],"return":[7,9,10,13,15,18,20,23,37,42],"short":[1,11,35],"static":[8,16,34,50],"switch":[28,37,38,42],"true":[3,5,7,9,10,11,13,15,18,20,21,22,45],"try":[1,5,7,11,13,15,16,18,20,23,28,30,37,38],"var":20,"while":[0,3,16,20,21,22,28,30,32,33,35,36,37,38,39,42,45,49,50],A:[5,9,11,14,15,16,18,20,21,22,24,26,30,33,34,35,37,38,39,42,44,49,50],AND:30,AS:20,And:[1,14,28,30,34,42,45,46,47],As:[0,1,3,5,7,9,11,15,16,18,20,21,22,23,28,29,33,35,38,39],At:[5,18,30,37,50],BY:49,Be:[3,32,42],But:[28,37,45,50],By:[5,9,11,13,15,18,21,22,23,28,29,32,33,38],For:[1,3,5,7,9,10,11,13,14,15,16,18,20,21,22,23,24,27,28,29,30,33,35,36,37,38,39,40,42,47,50],INTO:30,If:[0,1,5,7,8,9,10,11,13,15,16,18,20,21,23,24,28,29,30,32,33,34,36,37,38,39,40,41,42,46,49,50],In:[0,1,2,3,5,7,9,10,11,13,14,15,16,18,20,21,22,23,26,28,30,32,33,34,35,36,37,38,39,40,41,42,46,50],Is:38,It:[0,1,5,7,9,11,12,14,15,16,17,18,19,20,21,24,28,30,33,35,36,37,38,40,42,46,47],Its:23,NOT:[24,28,30,34,37],No:[20,22],Not:[16,37,47],OR:42,Of:[15,28,45],On:[0,3,5,20,21,26,28,30,37,38,42,46],One:[3,5,9,13,15,16,18,24,28,29,30,34,42],Or:[1,13,28,41],Such:[1,42],That:[14,16,28,30,37,45,50],The:[0,1,3,6,7,8,10,11,13,14,15,16,19,20,21,24,26,28,29,32,34,36,37,38,39,40,45,47,49,50],Then:[15,29,30,38,41,42,46,50],There:[1,4,5,7,9,10,11,13,15,16,18,20,22,23,24,28,29,34,35,38,40,42,45,50],These:[0,1,3,5,6,9,11,14,16,18,20,21,22,23,28,30,34,38,42,50],To:[0,1,3,5,7,9,10,11,13,15,16,18,20,21,23,24,26,28,30,33,34,37,38,39,40,41,42,45,50],With:[5,13,15,18,38,39,41,42],_2:42,_:42,__getitem__:18,__resample_dim__:20,_build:0,_check_indexing_error:18,_coordinateaxistyp:23,_coordinatezisposit:23,_engin:18,_fillvalu:5,_getitem_multilevel:18,_lib:18,_netcdf4:5,a_221_19930313_0000_000:23,aa:15,ab:[9,13,15],abbrevi:[7,18,23,39],abernathei:3,abil:[2,15,16,17,21,23,34,47],abl:[5,7,11,13,16,34,36,37,50],about:[0,1,5,9,13,15,18,20,21,22,23,30,33,34,35,36,37,38,39,40,41,42,44,46,47,50],abov:[0,1,3,5,7,9,11,13,15,18,20,21,22,23,28,30,32,34,37,38,39,41,42,45,50],absolut:[15,38],accept:[11,18,30,38],access:[1,3,5,9,13,14,18,21,22,32,34,37,39,41,42,45],accident:[15,28],accomplish:[15,37,38],accord:[13,14],account:[0,9,25,28,30,32,35],accur:[3,7,11,22],achiev:[16,18,21,37],acknowledg:[20,22],acquir:38,across:[9,11,13,14,15,20,22,23,26],action:[28,29,32,33,35,37,38],activ:[0,24,30,34,35,36,37,38,41,46,50],activity_id:[20,22],actual:[0,5,7,9,10,14,15,16,21,22,28,33],acut:28,ad:[1,3,5,14,15,16,21,23,28,29,30,32,33,37,38],adapt:[7,21,23,35,47,49],add:[1,5,7,10,11,15,23,28,29,30,32,34,35,37,41,42],add_featur:3,add_offset:5,addit:[1,2,3,5,7,9,15,16,18,20,21,22,23,24,28,33,35,36,37,38,39,41,50],addition:36,address:[33,36,37,38,39],adjust:1,admin_1_states_provinces_lak:3,administ:32,admonit:1,adopt:25,advanc:[10,13,14,15,16,19,23,45],advantag:[7,23,24,39,42,47],advect:13,advent:39,advertis:33,advic:28,aerosol:[20,22],affect:[20,24,28,30,32],after:[1,5,7,10,11,13,18,20,21,23,28,29,30,32,34,37,38,42,50],afterward:30,ag:45,again:[1,3,7,14,18,20,21,28,30,32,36,37,42,50],against:[22,28],aggreg:21,agnost:[19,23,24,40],agre:28,ahead:[30,34],aid:5,aim:30,air:[5,20,23],air_pressur:[5,23],air_pressurearrai:23,air_pressurexarrai:23,air_temperatur:[5,23],air_temperaturearrai:23,air_temperaturexarrai:23,aka:[32,34],albani:[5,28],alert:34,algebra:[12,14,16],algorithm:[14,20],alia:[7,28],alias:[7,29],align:[1,5,9],all:[0,1,3,5,7,8,10,12,13,14,15,16,17,18,20,21,22,23,24,28,30,33,34,35,36,37,38,39,42,44,45,47,49,50],allevi:7,alloc:21,allow:[1,3,5,7,9,10,11,15,17,18,19,20,21,22,23,24,26,29,30,32,34,35,38,40,41,42,44,45,50],almost:[7,9,15,45],alon:42,along:[1,5,11,13,14,15,20,28,37,40,42],alongsid:[11,18],alpha:[3,22],alreadi:[0,5,7,18,20,21,23,28,29,30,34,37,39,40,47],also:[0,1,3,5,7,9,10,11,13,14,15,16,18,20,21,22,23,24,28,30,32,33,34,35,36,37,38,39,40,42,45,47,50],alt:1,alter:15,altern:[22,40,41,42],although:[3,7,18,21,23,39],altitud:[5,20,23],altogeth:21,alwai:[7,9,15,16,28,30,32,35,37,40,42,45,49],am:7,ambigu:7,america:23,american:[3,22,23],among:[1,5,6,11,14,21,27,28,35,40],amount:[3,5,21,38],an:[0,1,5,7,9,12,13,15,16,18,19,20,21,22,23,24,26,28,29,30,32,33,34,35,37,38,39,40,41,42,45,47,50],anaconda:[24,40,41,46],analog:[21,22],analys:[2,16],analysi:[16,17,19,20,21,23,24,40,47,50],analysis_script:28,analysis_script_09122021:28,analysis_script_09122021_edit:28,analysis_script_new:28,analysis_script_old:28,analyt:19,analyz:[5,7,15,20],andrew:16,angel:28,ani:[0,1,5,9,14,15,16,18,20,21,22,23,24,28,29,30,32,34,35,37,38,39,42,45,50],anim:[8,11],annot:[5,8,10],announc:33,annual:20,anomali:18,anonym:39,anoth:[3,7,8,11,13,15,16,18,21,23,28,30,32,34,35,37,38,42,50],answer:[8,38],anyon:[21,23,32,38,47],anyth:[0,18,23,28,30,35,37,42],anywher:36,aogcm:[20,22],apach:49,api:[5,14],appeal:[9,21],appear:[0,1,7,9,21,23,26,38,42,50],append:[7,14,15],appendix:0,appetit:45,appl:45,appli:[7,15,16,21,22,47,50],applic:[5,15,26,38,42],apppli:30,appreci:38,approach:[7,20],appropri:[1,3,23,37,49],approv:[29,34,38],approxim:[1,15,21],ar:[0,1,2,3,4,5,7,8,9,10,11,13,14,15,16,17,18,20,22,23,25,26,28,29,32,34,36,37,38,39,40,41,42,45,46,47,49,50],arang:[5,9,11,13,15],arbitrari:[7,23],archiv:49,area:[20,21,22,28,42],areacello:[20,21,22],areacelloarrai:22,areacellocell_method:[20,21,22],areacelloforcing_index:[20,22],areacellolong_nam:[20,22],areacelloprov:[20,22],areacelloxarrai:20,aren:16,arg:13,argmax:20,argmin:[15,20],argsort:13,arguabl:39,argument:[3,5,7,9,10,11,15,18,20,21,23,45],aris:[20,22],arithmet:[7,21,23,45],arm:16,around:[11,14,28,40,45],arr:21,arrai:[5,9,10,11,12,15,16,18,19,20,22],arrang:[5,7],arriv:7,arrow:[9,29,34,42],art:16,as_strid:15,ascend:18,ascii:18,asid:28,ask:[8,11,16,30,33,37,38,39],aspect:[7,36,42,47],aspir:50,asset:[37,39],assign:[5,13,18],assist:[5,21],associ:[3,5,9,10,11,16,18,23,35,37],assort:12,assum:[5,7,11,18,21,29,30,32,37],assumpt:50,astimezon:7,astyp:21,atlassian:37,atmoschem:[20,22],atmospher:[16,20,22],attach:23,attempt:[10,16,18,38],attent:[23,28],attr:23,attribut:[1,3,5,7,11,19,20,21,22],august:7,authent:26,author:[1,28,30,37,38,50],authorship:1,auto:42,autom:[29,38],automat:[1,9,11,16,18,20,21,23,28,30,32,34,38,41,42],autopct:10,avail:[0,1,5,8,9,11,16,21,23,37,38,39,47,50],averag:[15,18,20,22],avg:15,avoid:[1,7,16,18,23,28,36,37],awai:42,await:30,awar:[1,20,23],ax2:[3,11],ax:[9,10,20,22,23],axdict:9,axes_dict:9,axhlin:22,axi:[5,10,11,13,14,15,20,21,22,23],axvlin:22,azimuth:15,b2a3b61:37,b:[7,9,13,14,15,20,21,22,28,37,42,49],back:[13,15,20,26,30,36,37,42,47],backend:8,background:[33,42,50],backward:14,bad:42,band:9,banihirw:49,bar:[16,21,37,42],base:[7,9,11,15,16,18,20,21,23,24,26,28,32,34,36,37,39,40,42,45,47],basi:38,basic:[2,6,7,8,10,12,13,15,16,19,20,21,23,25,29,30,32,34,36,37,38,40,50],bb:15,bbox:9,becaus:[1,5,7,8,9,20,21,23,24,28,30,33,34,39,45,47],becom:[3,14,15,20,21,23,29,30,34,36,50],been:[0,3,18,21,23,28,29,30,32,33,37,39,42],befor:[1,2,5,7,9,14,15,18,20,21,22,23,28,29,30,34,37,38,39,42],begin:[1,3,5,9,11,13,14,15,22,30,34,36,37,45],beginn:28,behav:[9,15,18,20],behavior:[10,15,20,21,23],behind:[7,23,33,37,47],being:[9,11,15,16,18,23,28,30,33,38,42,50],believ:50,bell:40,below:[1,3,5,7,9,10,13,15,18,21,22,23,24,28,32,33,35,36,37,38,41,42,45,50],benchmark:7,benefit:[23,47,50],best:[7,9,24,25,28,30,34,37,40,46],beta:1,better:[1,9,18],between:[2,7,9,11,13,14,20,21,23,26,28,37,40,42],beyond:[7,19,23],bf89419:37,bgcsub_experi:[20,22],bhist:[20,22],bigger:[3,15],biggest:40,bilinear:11,billion:[7,21],bin:[9,10,20],binari:7,binder:[9,42,45],biogeochemistri:[20,22],bit:[14,16,32],bitwis:[13,20],bitwise_and:13,bivari:11,black:[3,9,11,18,22],blank:10,blind:9,blink:42,blit:10,bloat:15,block:[7,9,10,11,14,45,50],block_siz:15,blog:21,blue:[3,9,10,11,13,22,42],bodi:[1,3],bonu:16,book:[1,8,18,20,21,22,29,30,35,40,44],boost:38,border:3,borrow:19,bot:29,both:[1,5,9,10,11,15,20,21,23,30,32,37,41,42,43,50],bother:7,bottom:[1,9,42],boulder:[5,20,22,23],bound:23,boundsunit:[20,22],box:[1,18,33,34,36,38,42],boxstyl:9,brace:45,bracket:[18,23,45],branch:[20,22,25,29,32,33,38],branch_method:[20,22],branch_time_in_child:[20,22],branch_time_in_par:[20,22],brancha:[30,34],branchb:30,brand:[28,45],brian:28,brief:[1,23,40,44,50],briefli:[1,5,7,28,37],bring:[23,30,33],broad:[5,16,20],broadcast:[14,23],broadli:50,broken:[21,33],brose:28,brought:[30,48],brown:3,brows:[28,35,39],browser:[0,10,26,32,37,38,39,40,41,42,50],bug:[21,33,35,37,39],build:[1,7,8,29,32,39,50],built:[2,5,6,7,8,14,16,17,20,21,23,32,45,47],bulk:[14,20,47],bullet:42,bunch:16,burden:23,button:[0,29,32,33,34,37,38,41,42],bwr:11,bytes_per_item:15,c1:9,c2:9,c3:30,c4:30,c:[5,9,10,11,14,20,22,28,41,42,45,47],cach:[18,20,23],calcul:[13,16,18,20,23],calendar:[6,7,16,20,21,22],call:[3,5,7,9,10,11,13,14,15,16,18,20,21,23,24,28,29,30,32,33,35,38,41,42,45,46],callabl:3,cam6:[20,22],camron:49,can:[0,1,3,5,7,9,10,11,13,14,15,16,18,20,21,22,23,24,26,28,29,30,32,33,34,35,36,37,38,39,40,41,42,45,46,47,49,50],cannot:[7,11,18,23,30,42],canva:3,capabl:[8,10,14,15,18,20,23,37,39],capit:[10,47],captur:[5,14,37],cardiac:16,care:[15,24,42],carpentri:[28,36],carre:3,cartesian:3,cartopi:[1,16,22,23],case_id:[20,22],casted_kei:18,cat:45,categori:3,caus:[1,18,21,23,42],cbar:9,cbcmap:9,cc:[9,49],ccmap:9,ccr:[3,22],cd:[30,32,37,46,50],cdl:5,cdm:23,cdot:13,ceas:36,celebr:26,celesti:7,cell:[1,3,9,10,13,14,15,18,20,21,22,40,41,45,50],cell_areatime_label:[20,22],cell_measur:[20,21,22],cell_method:[20,21,22],celsiu:[5,18,20,23],center:[9,12,15,20,22],centerpiec:28,centr:23,central:23,central_latitud:3,central_longitud:[3,22],centuri:[20,22],certain:[5,13,15,18,21,23,36,38,42],certainli:15,cesm2:[20,22],cesm2_grid_vari:[20,22],cesm2_sst_data:[20,21,22],cesm2parent_time_unit:[20,22],cesm2source_typ:[20,22],cesm_cmip6:[20,22],cf:[20,22,23],cf_role:5,cfdm:5,cfeatur:3,cfgeom:5,cfgridwriter2:23,cftime:[5,6,16,20,21,22],cftimeindex:[20,21,22],chain:18,chanc:7,chang:[0,3,9,10,11,15,18,20,21,22,23,24,30,32,33,34,35,36,38,39,41,42,45,46,50],chapter:[6,30,34,37,50],charact:[5,42],character:18,characterist:5,charg:39,chart:[8,11,16],cheat:[9,10,24],check:[0,1,9,13,14,15,16,20,24,29,30,34,35,38],checkout:[28,29,30,37],checksum:28,choic:[9,10,28,30,37,40],choos:[3,9,27,33,36],chose:[28,40],chosen:[10,20,21,22,23,29,46],chrome:41,chunksiz:21,chunktyp:21,cice5:[20,22],circl:[11,38,42],cism2:[20,22],citat:[1,20,22],clabel:11,clarif:[1,5,29],clarifi:29,clariti:5,classic:[14,41,46],clat:3,clean:[14,28],clear:[1,5,11,18,20,37],clearer:[15,18,23],clearli:[5,7,38],clever:[13,15,18],click:[0,9,23,29,32,33,34,36,37,38,41,42,45,50],climat:[5,7,16,20,22],climatolog:22,clipboard:32,clm5:[20,22],clon:3,clone:[0,25,28,29,30,33,34,36,38,50],close:[5,20,21,22,23,28,33,34,41,42],closer:[3,42],closest:[15,20],cloud:[7,27,32,45],cluster:16,clutter:36,clyne:[37,49],cmap:[9,11,20,22],cmip6:[20,22],cmip6model_doi_url:[20,22],cmip6parent_source_id:[20,22],cmip:[20,22],cmipbranch_method:[20,22],cmipparent_experiment_id:[20,22],co:[3,9,13,14,20,22,23,50],co_temp:23,coarsen:20,coast:22,coastlin:[3,22],code:[1,3,5,7,9,10,11,15,16,17,18,20,21,23,26,27,28,30,32,33,34,36,37,38,39,40,41,46,47,49,50],codebas:[28,30,47],codeown:38,cohes:1,col:15,cold:22,colder:9,collabor:[25,28,30,32,33,34,35,37,38,39,44],collaps:[23,42],collect:[9,23,24,35,37,45,48,50],collector:10,collis:30,colon:[14,45],color:[3,9,10,18,22,45],colorado:23,colorbar:[3,8,11],colormap:[10,11],colormap_nam:9,colorset:11,column:[11,13,14,15,16],columnar:18,com:[1,3,18,20,23,30,32,35,36,37,50],combin:[5,7,9,13,15,16,22,23,28,30],come:[7,13,15,16,21,23,24,28,35,40],comfort:[40,50],comma:[17,18],command:[0,30,32,36,37,39,40,41,45,46,50],comment:[0,1,5,9,20,21,29,33,34,37,38],commit:[1,29,30,32,34],common:[5,9,11,14,15,16,17,20,22,23,28,34,35,37,39,42,49],commonli:[4,5,7,10,11,18,20,23,37,38,47],commun:[8,14,16,20,21,23,25,26,32,33,34,37,39,44,47,49,50],compani:16,compar:[7,9,13,15,29,30,34,37,42,47],comparison:[18,28],compartment:30,compat:[15,47,50],compet:30,compil:47,complement:13,complet:[1,5,7,13,18,20,21,28,29,32,33,37,42,50],complex:[7,9,16,20,21,35,37,38],compliant:5,complic:[10,37],compon:[7,13,20,23,26,30,40,50],component_of_wind_isobar:23,composit:9,comprehens:[6,8,11,45],compress:[5,32],compris:1,comput:[7,12,14,16,19,23,26,28,29,30,32,35,36,37,40,42,44,45,47,50],computed_d:21,con:36,concaten:21,concept:[1,5,7,9,10,11,13,14,15,16,18,20,21,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,46],conceptu:[15,35],concern:9,concis:[1,19,20],concret:13,conda:[26,27,41,42,46,50],condit:[13,18,22,45],conduct:47,config:[0,28],configur:[1,25,28,30,32,34,37,50],confirm:[1,9,15,41],conflict:[30,34,38],conform:[3,5,23],confus:[5,7,20,30,38],congratul:[32,41,46],conic:5,connect:36,consecut:5,consequ:42,conserv:[20,21,22],consid:[5,14,15,16,21,28,32,37,40,42],consist:[5,7,11,20,21,22,26,32,34,37,38,39],constant:[3,20,45],constrained_layout:9,construct:[0,4,6,9,15,17,31,43],constructor:9,consult:[20,22],consum:21,consumpt:49,contact:1,contain:[0,1,2,3,4,5,6,7,9,11,12,13,14,15,16,17,18,19,20,22,24,26,28,35,37,38,39,42,44,45,50],contempt:28,content:[0,4,6,17,23,28,29,30,32,34,37,38,39,42,43,49,50],context:[13,15,28,33,37,42],contigu:14,continu:[14,20,24,30,34,37],contour:[3,8,9,16],contourf:[3,9,11,20],contrast:23,contribut:[1,8,21,25,28,30,34,36,37,47],contributor:[28,29,30,34,37,38],control:[15,20,22,25,29,30,32,34,36,37,38,42,44],convei:[5,7,22,23],conveni:[7,18],convent:[5,14,20,21,22,23],convers:[5,7,9,18,33,34,35],convert:[3,5,7,9,10,13,14,15,18,20,21,22,23,33],convert_degc_to_kelvin:18,cool:25,coolwarm:22,coord:23,coordin:[3,7,9,15,19,20,21,22],coordinatestandard_nam:23,copi:[1,11,13,15,28,30,32,34,35,37,42],copyright:1,core:[3,11,12,14,18,21,23,29,44,50],corner:[11,42,50],correct:[1,7,9,20,21,23,38],correctli:[7,9,41],correspond:[3,5,9,10,13,14,15,16,18,20,22,23,30,36],corrupt:42,cos_csum:14,cos_sum:14,cos_t:14,cost:7,could:[3,9,14,15,16,18,29,32,33,34,35,42],count:[1,10,18,20,21,32,45],counterpart:18,countri:3,coupl:[20,22,37],cours:[15,28,45],cousin:32,cover:[1,3,5,6,7,9,10,11,14,18,20,21,22,23,24,28,30,32,34,35,38,39,40,41,42,46,48,50],coverag:[3,20,21,22],cowork:40,cpu:21,cr:[3,22],crash:18,creat:[1,7,8,10,13,15,16,18,20,22,25,26,32,34,35,36,38,40,41,42,45,46,50],createdimens:5,createvari:5,creation:[2,30,36],creativ:[20,22,49],creativecommon:[20,22],creator:33,credenti:36,credit:[1,49],criteria:36,criterion:18,critic:[23,28],crop:15,cross:[0,2,6,11,16,24,40,44],crucial:14,csv:18,ctrl:[41,46],cultur:3,cumbersom:23,cumsum:14,cumul:14,curli:45,current:[0,3,5,7,8,15,24,28,30,42,50],cursor:42,curv:22,custom:[1,8,22,42,47],customis:16,cut:42,cutoff:[20,21,22],cv:[28,39],cycl:[20,34],cyclic:9,cyclon:7,d2:[20,22],d67h1h0vnominal_resolut:[20,22],d:[0,5,7,9,10,11,14,15,21,22,30,32,34,41,42,46,49],d_2d:15,d_3d:15,da:21,dai:[5,20,22,23,28,45],dark:[18,42],darr:21,dash:11,dashboard:41,dask:[16,19],data:[1,2,6,10,13,16,17,19,22,24,26,35,42,47,50],data_var:23,dataarrai:[20,21,22],dataarraydim_0:23,dataarraygroupbi:20,dataarraylat:23,dataarrayresampl:20,dataarraytim:23,databas:[17,18],dataplot:3,dataseri:18,dataset:[3,5,9,11,16,18,19,20,21,22,29],datasetdimens:[20,22,23],datasetscan:23,datatyp:[5,47],date2num:5,date:[6,16,18,20,23,28,30,37,50],date_rang:23,datelin:22,datetim:[5,16,23],datetime64:[6,16,18,23],datetimeaccessor:20,datetimeindex:[18,23],datetimenoleap:[20,21,22],dawson:16,de:[8,25],deactiv:[24,50],deal:[6,9,15,16,23],dealt:38,debug:38,decemb:20,decent:3,decept:7,decid:[11,30,38,47],decim:[14,45],decis:45,declar:[5,11],decod:[20,21,22],decode_cf_vari:[20,21,22],decreas:[5,9,23],dedic:37,deeper:7,def:[10,18],defer:21,defici:47,defin:[0,3,5,7,9,10,13,18,20,21,22,23,45],definit:[2,3,9,21,22,42,45],deg2rad:[3,45],degc:[18,20,21],degcvariable_id:[20,21,22],degre:[3,7,14,18,20,21,23,45],degreeparent_activity_id:[20,22],degrees_east:[5,20,22],degrees_eastarrai:[20,21,22],degrees_eastlong_nam:23,degrees_north:[5,20,22],degrees_northarrai:[20,21,22],degrees_northlong_nam:23,delai:[21,29],delet:[10,24,28,33,35],delta:[15,32],delv:[3,7],demonstr:[5,9,10,13,15,18,20,21,22,23,28,30,32,34,42],deni:37,denot:[30,45],densiti:[9,10],depend:[3,5,11,20,21,24,28,29,30,33,34,37,41,46],deploi:[28,29],deploy:29,depth:[18,21],depthli:30,deriv:[10,12],describ:[1,5,7,9,10,11,13,15,18,20,21,23,28,29,30,37,38,50],descript:[1,5,7,18,20,21,22,23,28,29,37,38],design:[1,2,5,7,15,30,35,38,42,47],desir:[11,30,32,37],desktop:[32,35,42],despic:28,despit:[7,9],destin:[32,34,37],destruct:28,detach:28,detachedhead:28,detail:[1,3,5,7,9,10,18,20,21,23,25,27,28,33,34,38,39,41,42,44,45,50],determin:[7,21,35],dev:[18,20,21,22,50],develop:[16,19,21,24,26,28,30,33,37,39,40,47],deviat:[16,20,22,23],devic:21,devop:39,dew:11,dewpoint:11,dewpoint_1000:11,df:18,diagnost:21,diagram:[9,11,14,35],dialogu:42,dice:[15,17],dict:[9,18],dictat:35,dictionari:[9,18,23,28],did:[7,13,20,32,37],didn:23,diff:[7,28,37],differ:[1,5,6,9,10,13,14,18,20,22,23,25,28,30,32,33,34,37,38,39,40,41,42,50],difficult:[15,20,21],dig:7,digest:15,digit:[37,39],dilemma:33,dim:[14,20,22,23],dim_0:23,dim_1:23,dim_2:23,dim_2xarrai:23,dimens:[11,13,14,19,20,21,22],dimension:[5,14,16,18,19,21,23],direct:[5,18,30,34],directli:[0,2,16,18,21,28,37,42],directori:[3,28,32,37,41,42,46,50],directory_nam:37,disabl:36,disadvantag:[23,47],disc:[41,42],discard:[28,37],disciplin:[16,50],discov:14,discret:[5,9,12],discuss:[5,7,18,21,23,25,28,29,30,32,34,35,36,37,38,39,43],disk:[5,24],diskless:5,disori:7,displac:[20,22],displai:[1,3,9,10,18,21,23,30,36,37,38,40,42],dissemin:39,distanc:9,distinct:[21,23],distinguish:[28,44],distribut:[9,10,11,16,18,21,24],district:7,disturb:37,dive:[15,28],diverg:[9,37],divid:[1,21,22],doc:[3,5,13,20,21,22,30,45],docstr:28,document:[1,3,5,7,8,9,10,11,12,15,17,18,20,21,23,24,26,28,30,37,38,39,40,42],doe:[1,5,7,10,13,15,16,21,23,26,28,37,38,39,42,45],doesn:[7,9,30,37,42],doi:[20,22,49],domain:[3,5,14,19],don:[13,14,18,28,37,42,47,50],done:[3,7,9,18,23,28,32,37,41,42,45,47,50],dot:[1,13,18,21,22,23,42],doubl:[5,9,42,45],doublearrai:[20,21,22],doubt:39,down:[5,7,23,24,28,34,41,45],downarrai:23,downgrib_level_typ:23,download:[0,3,18,20,23,28,32,36,39,40,50],downsampl:20,downsid:9,downward:[20,22],downxarrai:23,dozen:26,draft:29,drag:42,dramat:21,draw:9,draw_label:3,drive:[20,22],driver:39,drop:[22,34,45],dropdown:[9,42],ds:[20,21,22,23],ds_1000:23,dsg:5,dstack:13,dt:[7,20],dtype:[14,15,18,20,21,22,23],due:[5,7,23],dump:5,durat:7,dure:[7,30,38],dynam:[20,22,45],e21:[20,22],e56ea58071f150ec00904a50341a672456cbcb8f:28,e:[1,5,7,9,10,11,13,14,15,16,17,18,20,22,26,28,30,32,35,37,38,39,42,46,49,50],each:[1,3,5,9,10,13,14,15,16,18,20,21,23,24,28,30,35,36,37,39,40,42,45],eager:21,earli:[20,22],earlier:[3,5,7,18,20,21,23],earth:[2,5,7,20],eas:9,easi:[7,17,28,33,35,36,37,40,47,50],easier:[7,15,16,23],easili:[5,7,9,13,18,21,23,36,38],east:16,ec:9,ecosystem:[5,12,14,15,16,28,35,39,40,44,48,50],edc31c0:28,edg:15,edgecolor:[3,9],edit:[0,1,5,28,30,32,37,38,42,46],editor:[28,32,37,38,40,46],edu:28,educ:[48,50],educreation_d:[20,22],effect:[3,7,10,18,20,28,37,38,40,44,49,50],effici:[6,15,16,18,19,38,47],effort:[7,10,16,21],either:[1,5,7,15,18,20,22,30,32,34,37,38,45],el:[18,22],elabor:18,element:[1,3,5,11,13,14,15,16,20,23,38],elimin:3,els:[1,20,33,45],elsewher:[28,33,43],email:39,emb:1,embed:21,emphas:45,emploi:[7,37],empti:[3,10,15,21,30,42],enabl:[1,14,18,20,22,30,32,35,36,39,44,50],enclos:20,encount:[8,14,28],encourag:[1,20,22,24,30,40],end:[1,3,7,9,13,14,15,16,20,22,30,31,33,37,42,45],energi:[20,22],engin:[21,26],england:7,english:7,enhanc:[15,28,42],enough:[20,22,40],ensembl:[20,22],enso34:18,enso:18,enso_data:18,ensur:[3,5,9,20,22,24,29,37],enter:[30,38,41,42,45,50],entir:[3,14,18,20,22,28,42],entireti:[0,14],entri:14,enumer:[1,15,32],env:[0,18,20,21,22,24,50],environ:[1,11,26,30,37,39,40,41,42,44,45,46,50],environment:23,eof:16,ep:16,epoch:7,equal:[11,18,20,28],equat:[1,10,13,15,21,26,42],equatori:22,equival:[1,3,7,14,18,20,42],eroglu:49,err:18,errat:21,error:[0,1,7,19,20,23,28,30,37,38,42],es:[20,22],esc:46,esd:5,esoter:7,especi:[0,1,3,9,18,36,39,41,47],essenc:30,essenti:[1,7,18,23,24,28,30,32,50],establish:[30,36,39,47],estat:42,estim:[1,15],etc:[1,11,16,20,24,26,30,33,40,42],europ:3,even:[3,7,9,10,13,14,15,18,33,34,36,37,40,42,50],evenli:[3,14],event:[7,22,33,34],eventu:[0,40],ever:[28,47],everi:[5,7,13,14,20,23,24,28,34,37,38,47],everyon:[30,37,48],everyth:[16,20,24,30,41,42],evolv:[20,22,37],ex:9,exact:[3,7,20,22,32],exactli:7,exagger:3,examin:[7,33,38],exampl:[5,9,10,11,13,14,15,16,18,20,21,22,23,24,25,28,29,30,32,33,34,36,37,38,39,41,42,44,45,47],exce:22,excel:37,except:[1,3,5,10,18,23],excerpt:5,exclud:[20,22],exclus:[14,18,20],execut:[7,9,15,21,30,40,42,45,46,50],exercis:7,exis:30,exist:[1,5,7,14,16,18,20,21,23,30,32,33,35,37,38,42],exit:[28,29,42,46],exp:[11,15],expand:[13,14,18,23],expect:[7,15,24,29,37,42,50],experi:[11,19,20,22,30,32,50],experienc:39,experiment:[7,28],experiment_id:[20,22],explain:[23,28,34],explan:[9,26,35],explanatori:40,explicit:32,explicitli:[9,14,38,50],explod:10,explor:[9,10,14,15,18,23,26,28,35,39,42],exploratori:17,express:[3,7,9,15,17,20,21,22],extend:[13,14,16,45],extens:[8,13,29,42,43],extent:[3,5,11,20,22],extern:[1,37],extra:[1,5],extract:[13,14,16,18,20,23],extran:3,extrem:[8,21],f09_g17:[20,22],f98d05e312d19a84b74c45402a2904ab94d86e45:28,f:[0,5,7,9,15,20,21,42,45,50],f_:15,f_i:15,face:3,facecolor:[3,9],facil:16,facilit:[33,39],fact:[3,7,8,15,18,23,28,34,35,38],facto:[8,25],factor:40,fail:[21,37],fairli:21,fake:[9,11],fall:18,fals:[10,13,18,20,23,28,45],familiar:[1,5,7,8,9,13,14,18,19,20,21,23,29,30,38,40,42,44,46,47],fanci:9,far:[5,16,21,42],fashion:[20,21,23],fast:[12,17,18,34,45],fatal:[30,37],favorit:[0,28,37,40,46],fc:9,featur:[2,5,7,8,9,11,14,16,17,18,21,22,23,30,32,33,39,42,44],feature_artist:3,feature_interfac:3,featureartist:3,featuretyp:[5,23],februari:20,feed:10,feedback:[0,29,30,35,38,39],feel:[0,1,9,37,38,42,45,50],fetch:[18,20,21,22,23,30,32,34,37],few:[1,7,11,14,18,28,29,32,45,50],field:[13,15,16,20,21,22,47],fieldtitl:[20,22],fifth:13,fig:[3,9,10,11,20,22],figsiz:[3,9,11,18,20,22],figur:[9,10,18,20,22,23,38,50],file:[0,3,4,7,10,11,16,17,18,19,20,22,23,26,28,29,30,32,34,35,36,37,38,39,41,42,46,50],filepath2:[20,22],filepath:[18,20,22,23],fill:[9,21,22,28,33,42,47],fill_between:22,filler:5,filter:[16,20,33,36],filterwarn:3,find:[1,3,7,8,10,11,13,14,16,18,20,21,23,24,26,28,29,30,38,42,45],finish:[1,28,34,38,40],finit:[5,15,20,22],firm:7,first:[0,2,3,5,6,7,8,9,10,11,13,14,15,16,18,20,22,27,28,29,30,32,36,37,38,39,41,42,44,46,50],fit:[15,20,22],five:[18,28,32],fix:[5,7,14,20,22,28,29,33,37],flag:28,flat:3,flaw:7,flexibl:[16,17,45],flip:14,float32100:23,float321:23,float3228:22,float32:[5,20,21,22,23],float32nan:20,float640:[20,21,22],float64190:22,float641:22,float6425:23,float64288:23,float64997:23,float64:[5,14,18,20,21,22,23],flow:30,flowchart:30,fluffi:45,flux:[20,22],focu:[15,23,38],focus:[19,23,33,50],folder:[35,41,42],follow:[0,1,3,5,7,9,10,11,14,15,18,20,21,22,23,24,26,28,29,30,32,33,34,35,36,37,38,39,40,41,42,44,46,50],font:11,fontsiz:[9,45],forc:50,ford:49,forecast:[5,11,23],forecast_model:5,forecast_tim:5,foreign:5,forget:14,forgotten:42,fork:[25,28,29,33,35,36,38],form:[0,1,3,5,9,18,19,20,28,37,50],formal:[16,34],format:[1,5,8,9,11,16,18,23,38,40,42,45,50],format_byt:21,former:37,formerli:19,forth:35,fortran:[14,45,47],fortun:[20,33],forum:[34,37,39],forward:[7,15,42],foss:28,foster:39,found:[0,1,3,5,7,20,22,29,33,50],foundat:[1,2,8,16,29,33,35,41,44,46,49],four:[9,21,26,34],fourier:[12,14],frac:[9,42],fraction:[20,21,22],frame:[10,18],framework:37,free:[0,1,9,21,25,28,36,40,41,42,45,47,49],freeli:[28,39],french:[3,7],freq:[18,20,21,22,23],frequenc:[18,20,21,22],frequent:[0,7,15,23,24,28],fresh:42,friend:[7,28],friendli:[7,9,18],from:[0,1,2,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,26,28,29,30,32,33,34,35,36,37,39,40,41,42,50],from_list:9,front:1,frustrat:7,fstr:9,ftp:39,full:[0,3,9,14,15,18,20,23,24,39,41,50],fullest:[20,22],fulli:[1,6,7,23,28],fun:[19,37,45],fundament:[12,14,23,47,50],further:[3,5,7,13,15,20,22,35],further_info_url:[20,22],furtherinfo:[20,22],furthermor:9,futur:[30,47],fx:20,fxid:[20,22],fxing:28,g:[5,7,9,11,15,16,17,18,20,22,26,28,32,35,38,39,46],gain:20,galleri:[9,11,16,23],gap:47,garbag:10,garner:18,gatewai:14,gather:[24,42],gaug:5,gb:[20,21,22],gca:9,gear:38,gener:[0,1,5,7,9,10,13,16,18,20,21,22,24,28,33,34,35,37,39,40,42],generating_process_or_model:23,geo:[3,16],geocat:[3,9,11],geograph:[2,3,16,22,23],geoi:23,geometri:5,geophys:7,geopotenti:23,geopotential_height_isobar:23,georefer:3,geoscienc:[4,7,16,19,23,25,50],geoscientif:[7,17,18,20],geospati:2,geospatial_lat_max:23,geospatial_lat_min:23,geospatial_lon_max:23,geospatial_lon_min:23,geox:23,geoxarrai:23,geoyarrai:23,get:[3,7,10,13,14,15,16,28,29,30,33,34,36,37,38,41,42,44,47,48,50],get_item:18,get_loc:18,getitem:21,gf:11,gib:21,gif:10,git:[25,29,32,34,36,38,39,44],github:[0,1,5,18,20,21,23,30,32,33,38,43,44,49,50],githubusercont:50,give:[1,11,13,16,18,21,28,35,37,43,49],given:[14,15,16,20,21,28,37,42],gl:3,global:[5,7,20,22,28],globe:5,gmt:7,go:[1,7,8,10,15,29,34,35,36,42,46,50],goal:[11,16,22,26,37],goddamn:28,goe:[23,42],gone:42,good:[0,1,13,18,24,28,34,37,38,42,45,46,50],good_heat_index:13,googl:[0,7,30],got:45,gov:[20,22],govern:[20,22],gpmdescript:23,grab:13,grad_vector:13,gradient:13,gradient_i:13,gradient_x:13,grai:[3,18,42],graph:[5,16,21],graphic:[8,21],grayscal:9,grb:23,great:[15,16,24,26,28,33,40,47],greater:[9,18,20,21,23,34,45],greatest:39,greatli:[1,14,16,20,23,28],green:[9,10,11,32,34,38],greenwich:7,gregori:5,gregorian:7,grei:9,grib1_cent:23,grib1_level_desc:23,grib1_level_typ:23,grib1_paramet:23,grib1_subcent:23,grib1_tablevers:23,grib:[4,16,23],grib_level_typ:23,grib_table_vers:23,grib_variable_id:23,gribcollect:23,grid:[3,16,19,20,21,22,23,45],grid_map:[5,23],grid_mapping_nam:[5,23],gridhistori:23,gridlin:[3,11,22],grinitialization_index:[20,22],group:[5,16,18,20,22,23,47,48],groupbi:[18,22],grover:49,grow:[5,14,19],gt:[20,21,22,23],guarante:24,gui:[24,30,41],guid:[1,6,13,14,15,18,20,29,32,33,35,37,40,42,50],guidelin:0,gx1v7:[20,22],h:[5,7,15],ha:[1,3,5,7,9,10,11,13,14,15,16,18,20,21,22,23,28,29,30,32,33,34,35,37,38,39,42,45,47],habit:36,had:[33,34,39],half:14,hand:[5,13,14,18,20,21,28,32,33,45],handi:24,handl:[7,15,16,22,23,38],hang:1,happen:[7,30,47],happili:7,hard:11,hardbound:50,hardcopi:11,harken:47,has_year_zero:[20,21,22],hashtabl:18,hashtable_class_help:18,hasn:42,have:[0,1,2,3,5,9,10,11,13,14,15,16,18,19,20,21,23,24,26,28,29,32,33,34,35,36,37,39,40,41,42,45,46,47,50],haven:[28,39],hdf5:5,hdl:[20,22],he:[16,28],head:[16,18,28,30,34,41,42],heartbreak:16,heat:[11,13,20,22],heatmap:11,heavili:19,height:[5,16,21,23],heightgrid_map:23,heights_var:5,hello:[1,30,32,37,41,42,45,46],help:[1,5,7,9,11,13,15,18,20,23,24,26,28,30,32,33,36,37,38,40,41,42,46],helper:14,helpfulli:18,henc:[14,23,37],henceforth:39,here:[0,1,3,5,7,9,10,11,12,13,14,15,16,18,19,20,23,24,26,27,28,29,30,33,34,36,37,39,40,41,42,45,46,47,50],hexadecim:28,hfsso:[20,22],hh:7,hi:16,high:[3,7,11],high_tid:7,higher:[3,23,47],highest:13,highli:[1,5,21,37,40],highlight:[22,42],hinder:38,hist1:9,hist2:9,hist2d:[9,10],hist:[10,18],hista:9,histb:9,histc:9,histogram:[8,9,11,18],histor:[20,22,30,37],histori:[5,8,23,30,37],historicalexternal_vari:[20,22],hit:42,hold:[5,7,14,21,23],home:[3,18,20,23,50],hood:[7,16,28],hope:[33,40],horizont:[3,5,9],host:[0,23,28,30,32,35,36,37,38,39,42],hotter:9,hour:[5,7,16,20],hous:[32,35,39],hover:[0,45],how:[0,3,4,5,9,11,13,14,15,18,20,21,23,24,25,26,28,29,30,33,35,37,38,40,41,42,43,44,45,46,47],howev:[1,5,7,9,10,15,18,20,21,23,28,32,34,36,37,38,42],hpa:[5,11,16,23],hpalong_nam:23,hpastandard_nam:23,html5:10,html:[0,1,3,10,11,42],http:[1,3,18,20,22,23,30,32,35,37,39,40,49,50],hub:42,hue:9,huge:[24,28],human:[7,18,43],humid:[13,15],hypothet:5,i:[1,7,10,11,12,13,14,15,16,18,20,30,32,37,42,45,50],ic:[20,21,22],icon:[32,36,38,41,42,45,50],id:[18,20,21,24,32,35,36,38,39],idea:[24,28,29,33,34,35,37,39,47],ideal:32,ident:[7,20,21,37,45],identifi:[5,15,18,38,45,47],idiot:28,idl:47,ignor:[3,7,15],illustr:[1,3,5,7,9,11,13,18,20,21,22,23,32],im:11,imag:[1,2,10,18,35,42],img:1,immedi:[21,24,29,32],impact:[28,32,37],impati:27,imped:21,imper:1,implement:[5,10,21],impli:[20,22,33],implicit:42,implicitli:38,impos:[20,22],improv:[1,3,7,9,15,23,35,38],impuls:16,imshow:11,inch:[5,11],includ:[0,1,2,3,5,6,7,8,9,10,11,12,13,14,16,18,19,20,21,22,23,24,25,27,28,30,32,33,35,37,38,39,41,42,45,46,47,50],inclus:[1,18,38],incompat:15,incorpor:[1,16,32,33,39],increas:[5,9,11,15,21],increasingli:[16,35],increment:[9,32],ind:[13,15],inde:23,indent:45,independ:[7,11,28,30],index:[0,5,10,11,15,16,20,21,23,28,37,45],index_col:18,index_nino34:22,index_nino34_rolling_mean:22,indexed_select:23,indexengin:18,indexerror:13,indic:[0,1,5,7,10,14,15,18,21,23,30,32,34,38,42],individu:[5,7,9,10,11,14,15,16,18,20,21,23,35,39,50],industri:[20,22],ineffici:21,inevit:[1,7],influenc:7,info:9,inform:[0,1,7,9,10,11,13,14,15,16,18,20,21,22,23,24,26,28,34,35,38,40,42,47,50],infring:1,infti:42,ing:14,inher:[7,21],inherit:[7,8],init:[10,28],init_func:10,initi:[5,7,11,21,33,34,37,42],initialis:[20,22],inlin:42,input:[7,16,20,24,28,38,39,42],insert:[1,37],insid:[9,34,37,40,41,42],insight:20,inspect:[14,45],inspir:[1,19],instal:[7,26,27,28,32,42,44,45,50],instanc:[5,13,15,16,20,41,42],instantli:50,instead:[1,3,5,10,15,18,20,21,23,36,42],institut:[5,16],instruct:[1,24,26,27,30,41,42,46,50],instructor:40,int32:[5,15,23],int641:20,int6440arrai:23,int64:[14,20,23],integ:[14,18],integr:[14,16,19,20,22,23,24,34,40],intend:[10,23,32,34,50],intens:47,intent:[18,30],intention:15,interact:[4,8,9,11,20,21,22,26,34,35,39,40,41,44],intercept:11,interchang:[18,21],interest:[5,9,21,29,32,33,36],interfac:[2,3,9,10,11,14,21,23,24,26,32,36],intermedi:[14,15,23],intern:[20,22],interoper:[5,24],interp:23,interpol:[9,11,16,20,22],interpret:[7,15],interspers:[1,42],interv:[5,7,9,10,11,14],interweb:45,intro:[1,18],introduc:[7,8,10,13,14,18,19,20,21,22,25,35,37,42,45],introduct:[5,8,15,20,21,22,50],introductori:1,intuit:[14,19,21],invalid:15,invalidindexerror:18,invent:16,invers:[5,13],invert:23,investig:14,invit:37,invok:[7,42],involv:[7,10,15,16,20,21,22,32,37,39],iosp:23,ipynb:[0,26,29,41,42,50],ipython:[7,21,42],irregular:13,is_integ:18,isd:[20,21,22],isel:[20,21,22],isn:42,isobar:[11,23],isobaric1:23,isobaric1pandasindexpandasindex:23,isol:[24,37],isotherm:15,issu:[0,1,3,7,9,18,21,25,28,29,30,32,34,35,36,37,38,39,43],item:[13,15,20,33,38,45],items:15,iter:[15,37,45],its:[2,3,7,8,9,10,13,18,21,22,23,24,28,30,32,33,35,36,38,39,40,42,47],itself:[14,18,35,38,39,42,50],j:[15,49],jan:[20,28],januari:[7,18,20,23],java:[7,23],join:[30,47],jonathan:5,journei:12,jukent:30,juli:16,julia:26,jump:42,jupyt:[1,3,7,9,10,18,21,23,24,25,29,38,42,43,44,45,46],jupyterhub:26,jupyterlab:[1,24,26,29,41],just:[3,5,10,11,13,14,15,18,23,26,28,29,30,33,34,36,37,39,40,41,42,45,46],k:[9,20,21,22,23,49],ka:7,kailyn:49,kalb:5,kbou:5,kdescript:23,keep:[1,5,7,8,11,16,20,24,28,33,36,37],kei:[1,2,8,9,16,17,18,20,21,35,37,45],kelvin:[5,18,20,23],kelvinstandard_nam:23,kent:49,kernel:[1,28,39,41],kevin:[28,30],keyboard:[41,42],keyboardinterrupt:42,keyerror:18,keyword:[3,7,9,10,11,18,20,21,23,45],kib:[21,32],kind:[37,42],km:5,km_coordinateaxistyp:23,know:[1,7,15,18,20,23,30,37,47,50],knowledg:[1,2,13,16,18,21,23],known:[1,5,6,7,11,18,20,21,23,35,37,38,39],koun:5,ktyle:28,kwarg:[3,9],l:49,la:22,lab:[1,41,50],label:[3,9,10,15,16,17,18,19,20,23,28,30,33,36,42,45],laboratori:[20,22],lack:[1,21,38],lai:[14,21],laid:[1,3,11,14],lake:3,lake_mask:3,lambert:[5,23],lambert_conformal_con:5,lambert_conformal_coniclatitude_of_projection_origin:23,lambert_project:5,lambertazimuthalequalarea:3,lambertconform:3,lambertconformal_project:23,lambertconformal_projectiongrib_variable_id:23,land:[3,20,22],land_mask:3,landic:[20,22],landscap:11,languag:[5,7,17,24,26,39,40,42,43,45,50],laptop:[27,30,32,35,45,50],larg:[7,9,13,16,18,19,20,21,23,38,47],larger:[10,15,21,34],last:[3,7,10,13,14,15,18,20,28,30,37,42,45],lastli:50,lat:[3,5,20,21,22,23],lat_0:5,lat_1:5,lat_bnd:[20,22],lat_bndslong_nam:[20,21,22],lat_var:5,later:[1,3,5,9,10,11,18,23,28,30,32,33,34,35,37,39,42,50],latest:[0,3,24,30,32,49],latex:[1,9,26,42],latitud:[3,5,16,20,22,23],latitude_coordinateaxistyp:23,latitude_of_projection_origin:5,latitudestandard_nam:[20,21,22],latitudeunit:[20,21,22],latn:3,latpandasindexpandasindex:[20,21,22,23],latter:37,latxlon:[20,22],launch:[26,41,42,50],launcher:[26,41,42],law:[7,20,22],layer:[3,13,20,21,22],layout:[8,10,42],lcc:5,lead:[1,3,23,34,37,39],learn:[1,3,5,7,8,9,10,11,12,13,14,15,18,20,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,44,46,47],learner:[8,32,37,40],least:[20,22,30],leav:[1,14,33],lectur:[13,14],left:[9,10,11,41,50],legal:1,legend:[9,11,20,22,45],len:[14,15],length:[5,7,10,14,18,20,21,22,23,42],lengthi:1,less:[3,10,16,19,20,21,24,47],lesser:[10,21],lesson:[28,32,34,37,39,40],let:[1,3,5,7,9,10,11,13,14,15,18,28,29,30,32,33,34,36,37,38,41,42,45,46],letter:[28,47],level:[5,7,9,11,15,21,22,23,50],leverag:[23,32,39],lgtm:38,liabil:[20,22],liabl:37,lib:[15,18,20,21,22],librari:[2,3,5,6,7,8,10,11,12,14,17,18,19,20,21,22,23,24,42],licens:[20,22,35,49],life:15,light:[9,28],lightgrei:9,like:[0,1,3,5,7,9,10,11,13,14,15,16,18,19,20,21,24,28,29,30,32,33,34,37,38,41,42,43,45,46],limit:[7,8,16,18,20,22,23],line2d:23,line:[1,2,3,5,10,13,14,15,16,18,23,24,28,30,32,36,37,38,39,41,42,45,46,50],linear:[12,14,16,30],linearli:[9,45],linearsegmentedcolormap:9,linestyl:[3,11,22],linewidth:[3,18,22],link:[1,3,10,15,21,23,32,33,34,36,38,39,45],linspac:[5,9,10,13,15,23,45],linu:[28,39],linux:[24,28,32,39,40,46],list:[0,1,3,5,7,9,10,11,13,14,18,20,21,22,23,24,28,32,34,36,38,40,42,47],listedcolormap:9,listlik:18,liter:7,littl:[37,50],live:[3,26,30,32,35,36,50],ll:[1,3,5,7,9,10,13,15,18,30,32,34,37,39,42,50],llnl:[20,22],load:[20,22,23,42,50],loc:[11,16],local:[3,7,18,28,29,30,32,34,35,36,37,38,42,45],locat:[5,9,11,15,20,22,23,32,45,50],log:[30,32,36,39],logarithm:47,logic:[12,20,45],login:36,logist:9,logo:[0,1],lon:[3,5,20,21,22,23],lon_0:5,lon_bnd:[20,22],lon_bndslong_nam:[20,21,22],lon_var:5,london:7,lone:3,long_nam:[5,20,22,23],longer:[3,5,24],longitud:[3,5,7,9,16,20,22,23],longitude_coordinateaxistyp:23,longitude_of_central_meridian:5,longitudestandard_nam:[20,21,22],longitudeunit:[20,21,22],lonpandasindexpandasindex:[20,21,22,23],lonw:3,lonxarrai:23,look:[5,7,9,11,13,14,20,28,33,34,37,39,42,45],lookup:15,loop:14,loos:21,loss:1,lost:[23,37],lot:[9,14,16,24,45],low:[7,47],lower:[1,11,47],lowest:13,ls:[30,42],lt:[20,21,22,23],lunar:7,lunar_dai:7,lw:22,m2:20,m2variable_id:[20,22],m:[5,7,14,20,22,23,28,29,37,42,45,49],m_avg:20,mac:[28,32,42,46],machin:[16,24,28,32,37,41,46,47,50],made:[7,15,23,28,29,30,33,34,36,37,38],magic:[7,15],magma:9,magnitud:7,mai:[0,7,8,9,10,11,15,18,20,21,22,23,24,25,28,30,32,33,34,35,36,37,38,39,40,42,49,50],main:[0,1,3,5,10,18,20,21,22,23,26,28,29,32,33,34,38,42,50],mainli:[23,38],maintain:[32,33,34,37,39],major:[14,23,45],make:[1,2,5,7,8,9,10,11,13,14,15,16,18,19,20,21,22,24,26,30,32,33,34,35,38,39,40,45,47,50],mam4:[20,22],manag:[1,21,22,27,28,30,33,35,37,38,39,42,50],mani:[1,3,4,5,9,10,11,14,16,18,20,21,23,28,30,36,37,38,39,40,45,47,50],manipul:[7,12,13,14,16,17,23,45,50],manner:[1,20,21,32,50],manual:[1,14,15,23,42],manuscript:37,map:[1,2,5,9,11,16,22,23],marbl:[20,22],mark:[1,33],markdown:[1,38,40,50],marker:11,markers:11,markup:42,mask:[12,13,22,29],masked_sampl:20,master:[30,37],match:[1,3,7,13,14,15,18,20,23,28,42],materi:[0,1,7,16,23,29,38,44,49,50],math:[7,13,42,50],mathemat:[9,12,14,23,47],mathjax:1,mathtext:9,matlab:[11,40,47],matplotlib:[2,9,10,13,16,18,20,22,23,39,50],matric:12,matter:9,max:[18,20],maxim:15,maximum:[16,20],mayb:28,mb:[11,20,21,22],md:28,mean:[1,5,7,11,12,14,15,16,18,20,21,22,23,28,33,34,36,39,40,42,47,49],meancom:[20,21,22],meaning:[23,38],meant:[38,42,45],meantim:18,meantime_titl:[20,21,22],meantitl:[20,21,22],measur:[7,16,42],mechan:[7,33,34],median:20,medic:16,meet:[9,13,18,28,36],member:[20,22,29,37,38],memori:[5,10,14,15,21,41,45,47],mention:[11,16,18,23,29,33,36,39],menu:[1,38,42,45],mercat:3,merchant:[20,22],mercuri:28,mere:7,merg:[9,20,22,28,29,32,33,34,37,38],merger:37,meridian:[3,7],merit:33,mesa:[20,22],meshgrid:[5,9,11],mess:28,messag:[0,1,28,30,34,37],meta:21,metadata:[1,5,16,23],meter:5,method:[5,9,10,11,13,15,18,21,22,30,38,41,45,50],metoffic:16,metpi:[5,14,36],mgrid:3,mib:[21,32],microsecond:[5,7],microsoft:30,mid:28,middl:9,might:[5,6,11,14,16,25,28,29,30,33,39,40,41,47,50],millennia:7,million:[7,21],min:[18,20],mind:[28,33,35],miniconda3:[18,20,21,22],miniconda:[24,41,46],minimum:[15,18,20],minor:0,minut:[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,46],mip_era:[20,22],miscellan:9,misinterpret:28,mispronunci:28,miss:5,missing_valu:5,mistak:[1,28],mix:[14,45],mkdir:[42,46],mm:7,mode:[29,37,46],model:[5,7,19,20,21,22,23,29,37],modif:37,modifi:[5,11,13,15,20,21,23,24,28,32,37,46,50],modul:[16,24,42],moment:[18,39,50],mon:[20,21,28],monfurther_info_url:[20,22],monid:[20,21,22],monitor:36,monochrom:9,month:[5,7,18,20,22],monthli:[18,20,22],monthpandasindexpandasindex:20,mood:28,more:[1,3,5,7,8,9,10,11,12,13,14,15,16,18,19,20,22,24,26,28,29,30,32,33,34,35,36,37,38,39,40,41,42,45,46,47,50],morlei:49,most:[0,1,5,6,7,8,9,10,11,13,14,15,16,18,20,21,23,28,34,37,38,39,40,44,45,47,50],mostli:[9,18],motiv:37,mountain:7,mountaintz:7,mous:50,move:[11,20,22,28,30,34,42],movement:26,mpl:3,ms:21,mu:9,much:[1,3,5,7,8,12,15,16,18,21,23,24,28,30,33,35,39,42,50],multi:[13,14,15,16,19],multi_mask:13,multidimension:[12,13,15],multilin:42,multipl:[7,9,11,13,15,18,21,22,28,30,42],must:[0,1,3,5,7,10,14,15,18,20,21,23,28,30,32,34,36,37,38],mv:28,my:37,mylist:45,mypet:45,mysci:[41,46],myweirdlist:45,n:[7,10,14,16,19,21,23,45],nabla:13,name:[1,3,5,7,9,10,11,18,20,21,22,24,28,29,30,32,33,37,38,41,42,45,46],named_select:23,nan:[13,20,21,22],nanarrai:20,nano:[40,46],narr:[1,23,26,40],narr_19930313_0000:23,narrat:42,nasa:5,nation:[20,22,23],nativ:[5,17,20,21,22],natur:[2,9,13,16,20,21,32,38,47],naturalearth:3,naturalearthdata:3,naturalearthfeatur:3,navbar:1,navig:[24,26,28,30,37,38,41,50],nbin:[9,10],nbyte:21,nc:[5,20,21,22,23],ncar:[3,20,22],ncarlicens:[20,22],ncdc:18,ncep:23,ncl:47,ncol:[9,11,20],ndarrai:[13,14,18,21,23,45],ndenumer:15,ndim:14,nearest:[7,20],nearli:[7,14,18],necessari:[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23,24,28,29,30,31,32,33,34,35,36,37,38,41,45],necessarili:[10,33],need:[0,1,3,5,7,9,10,13,15,16,18,20,21,23,24,25,28,29,30,32,35,36,37,38,39,41,42,44,47,48,50],neg:14,neglig:[20,22],netcdf4:[5,16,21],netcdf4_class:5,netcdf:[1,4,16,19],network:36,never:14,new_shap:15,new_var:[20,21,22],newaxi:15,newbranch:30,newli:[7,15,20,32,37],next:[0,9,21,22],next_high_tid:7,next_low_tid:7,next_slic:5,nice:[7,15,18],nicknam:[18,45],nino12:18,nino12anom:18,nino34:18,nino34_degk:18,nino34_seri:18,nino34anom:18,nino3:18,nino3anom:18,nino4:18,nino4anom:18,nino:18,nino_analyzed_output:18,nldn:7,nlevel:18,nnn:37,noaa:18,nois:18,noleap:[20,21,22],non:[1,5,6,14,16,20,21,22,28,30,34,49],none:[3,5,10,11,18,20,21,22,23,39],nonesub_experiment_id:[20,22],nonetable_id:[20,22],nonetime_titl:[20,22],nonlinear:9,norm:[9,22],normal:[10,11,22],normalized_index_nino34_rolling_mean:22,norman:5,north:[3,23],notat:[9,14,18,23,28,45],note:[0,1,2,5,7,9,10,11,13,14,15,18,20,21,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,45,46],notebook:[2,3,5,7,9,10,11,13,14,18,19,21,23,29,38,45],noth:[13,16,28,30,39],notic:[3,7,9,11,18,20,21,28,30,32,34,35,38,39,42],notif:[25,32,37],notifi:[33,36],now:[3,5,7,9,10,11,13,14,15,18,20,21,23,28,29,30,32,33,34,35,36,37,42,46,50],nowher:1,np:[3,5,9,10,11,13,14,15,18,20,21,23,45],npt:[9,10],nrow:[9,11],ns:[18,23],num:14,number:[1,3,7,9,10,11,13,14,15,16,18,20,21,28,33,35,37,40,42,47,50],numer:[5,7,8,9,11,16,20,23],numpi:[2,3,5,6,7,9,10,16,18,19,20,35],nvidia:39,ny:7,o:[11,12,32,46,49],obfusc:7,object2000:[20,21,22],object2014:20,object:[1,2,3,5,9,10,12,14,18,20,22,32,45],obs_data:5,obscur:47,observ:[20,22,23,50],obtain:[1,7,16,18,20,21,22],obviou:[5,21,37],obvious:15,oc:15,occas:21,occasion:[9,16],occur:[3,7,23,30,34,36,37],occurr:7,ocean:[3,16,20,21,22],oceansourc:[20,22],oceanstandard_nam:[20,21,22],ocnbgchem:[20,22],octocat:0,odd:15,off:[7,8,14,15,16,28,36],offer:[7,10,16,21,24,35,37,39,40,42],offici:[7,9,10,17,20,21,23,28,35,37,38,40,45],offset:[7,10],often:[1,7,8,15,16,18,20,21,23,28,30,32,33,35,37,39],oftentim:9,ofx:[20,22],ofxout_nam:[20,22],ok:37,okai:33,old:[5,33],oldest:37,omon:[20,21,22],omonout_nam:[20,21,22],omontracking_id:[20,22],onc:[1,3,9,11,18,20,23,24,29,30,32,34,36,37,42,50],one:[0,3,5,7,8,9,10,11,13,14,15,16,18,21,22,23,24,25,28,29,30,32,33,34,35,36,37,38,39,42,44,47,50],ones:[5,35],onli:[1,3,5,7,9,13,14,15,16,18,20,21,22,23,24,25,28,30,34,35,36,37,38,42,50],onlin:[7,16,39],onto:[15,20,22,47],open:[0,5,8,14,17,18,19,21,22,24,25,26,28,29,30,32,33,35,36,37,38,40,41,42,44,45,46,48,49,50],open_dataset:[20,21,22],opendap:23,oper:[6,7,8,12,13,14,15,16,21,28,32,37,42,47],operand:[14,15],opinion:38,opportun:[34,38],oppos:[18,36],opposit:7,opt:[36,50],optim:[1,21,23],option:[1,5,7,8,9,11,14,15,18,23,32,34,35,37,38,39,40,42,45,46],orang:[9,45],order:[0,1,2,5,7,9,10,11,13,15,16,18,20,21,23,28,29,30,36,37,39,42,44,45,50],ordinari:23,org:[3,20,22,35,49],organ:[14,15,18,28,29,32,35,39],orient:[2,3,5,7,11,28],origin:[3,9,11,13,15,18,20,22,23,28,29,30,32,34,35,37,39],original_own:37,original_repositori:37,originating_or_generating_cent:23,originating_or_generating_subcent:23,os:[28,32],oscil:18,other:[1,2,3,5,6,7,8,10,11,13,14,15,16,18,21,23,24,26,30,32,33,35,37,38,39,41,42,43,47,50],otherwis:[18,23,28],our:[0,3,5,7,9,10,11,13,14,15,17,18,20,21,22,23,29,30,32,34,37,41,42,45,46,50],ourselv:33,out:[0,1,3,6,7,9,11,13,14,16,18,20,21,23,26,29,33,35,38,42,44],outcom:37,outlin:37,output:[1,3,5,8,9,10,15,16,18,20,22,26,28,30,32,37,40,42,45,50],outputrealization_index:[20,22],outsid:[1,7,20],over:[0,14,18,20,22,23,28,37,39,42,45],overal:[1,22],overestim:1,overflow:[7,21],overlaid:3,overlap:[7,15],overload:7,overwhelm:[36,38],overwhelmingli:28,overwritten:5,own:[3,9,13,14,16,18,24,28,30,32,34,35,36,37,38,39,42,44,45,50],owner:[34,35],oxford:16,p:[7,14,15],pacif:22,pack:32,packag:[0,1,2,3,5,7,8,9,10,12,14,16,18,19,20,21,22,23,27,32,35,39,40,41,44,45,47,50],page:[0,3,5,7,9,10,11,16,18,21,23,28,30,33,34,36,39,42,45],pai:[23,28],paid:39,pair:[15,36],pairwis:15,panda:[6,7,14,15,16,19,20,23],panel:[11,41],pangeo:48,paper:37,paragraph:1,parallel:[3,16,19,37],paramet:[10,50],pare:24,parenthes:[13,20,45],parlanc:23,parsabl:5,parse_d:18,part:[0,1,3,5,7,9,10,15,18,23,32,34,35,42,50],parti:[7,37,38],partial:[7,10],particip:[21,25,36,39],particular:[5,7,9,13,15,18,20,22,36,37,39,50],particularli:[16,19,28,30,39],pass:[3,9,11,15,18,20,21,23,29,30,34],passag:7,password:[30,36,39],past:[15,20,22,28,32,39,42],pat:[30,36],patch:39,path:[18,23,30,37],patient:3,pattern:[9,15,22],paul:[30,49],pc:50,pcmdi:[20,22],pcolormesh:23,pd:[18,23],pdf:[9,16],peer:40,peopl:[1,9,28,30,38,40],per:5,percent:[10,20,21,22],percentag:10,perfection:1,perfectionist:1,perfectli:38,perform:[3,5,7,10,11,15,16,18,20,21,22,23,32,33,37,38,47],perhap:[33,37,39,40,47,50],period:[22,23,37],perman:28,permiss:[25,32,34,37],permit:[20,22],persist:5,person:[30,32,34,37],pertain:[1,7,21,38,42],perturb:[20,22],physic:3,pi:[7,9,10,13,14],pick:[9,28,33],picontrolparent_mip_era:[20,22],pictur:[18,26],pie:11,piec:[21,23,30,33,34],piechart:8,pillow:10,pink:[9,10],pisecond:7,pixel:[3,11],piyg:11,place:[1,9,13,14,32,36,37,42,43],placehold:[5,23],placement:3,plagiar:1,plai:[13,45],plain:[5,28,42],plan:[30,33,35],plate:3,platecarre:[3,22],platform:[0,11,24,25,26,27,28,39],pleas:[0,1,2,3,10,15,18,20,23,38],plenti:42,plot:[2,8,9,10,13,16,20,22,26,45],plot_temp:13,plt:[3,9,10,11,13,20,22,45],png:[1,11,16],point:[2,3,5,7,9,10,11,13,15,20,22,23,26,28,30,32,37,39,42,47],pointer:30,polar:[3,15],pole:[3,20,22],polygon:2,pop2:[20,22],popul:[1,32,38,42],popular:[8,11,16,18,19,20,23,26,38,39,40,42],portabl:47,portion:[5,18],posit:[5,12,13,15,23],possibl:[1,5,7,15,18,20,21,22,28,32,38],post:[35,36],potenti:[9,20,21,22,34,37,47],power:[2,13,14,15,17,18,23,28,32,35,37,50],pr:[29,30,33,34,37,38],practic:[0,7,9,13,20,23,24,25,30,34,37,38,42],pre:[1,3,20,22,28],preambl:44,preced:14,preceed:45,precip:5,precip_bucket_bound:5,precis:[23,37,38],predefin:5,predict:[23,24],prefer:[18,29,36,38,40,41],prefix:1,premad:9,premier:23,prepar:1,prepend:15,present:[5,19,20,26,40,42,50],preserv:[13,20],preset:20,press:[5,28,33,42,45,46,50],press_var:5,pressur:[5,15],pressure_coordinatezisposit:23,pressure_data:23,pretti:16,prettier:42,preval:35,preview:[15,18,29,38],previou:[1,3,5,9,10,15,18,20,21,22,23,29,30,37],previous:[20,30,32],primari:[37,38],prime:7,print:[1,11,13,14,18,20,37,41,42,45,46],printout:17,prior:[19,30,31,50],priorit:38,privat:35,pro:36,probabl:[7,10,18,37,47],problem:[15,21,23,26,30,33],proced:42,process:[1,2,7,10,15,16,18,20,21,23,28,29,32,34,37,38,42],prod:20,produc:[2,3,11,16,20,21,22,37,45,50],product:[1,13,20,38],prof:23,profici:[23,39],profil:[5,23],profile_id:5,prognost:[20,21,22],program:[5,7,16,17,18,21,26,40,42,47,50],programm:21,programmat:2,progress:21,progressbar:21,proj:[2,5],proj_var:5,project:[0,2,5,16,18,19,21,22,23,24,25,26,28,30,32,33,34,35,36,37,38,39,41,48,49,50],projectgrib_table_vers:23,projection_coordinateaxistyp:23,projection_x_coordin:5,projection_x_coordinateunit:23,projection_y_coordin:5,projection_y_coordinateunit:23,projectpythia:[18,20,23,29,32,34,35,36,37,50],projla:3,projlcc:3,projlcceur:3,projlccni:3,projmol:3,projpc:3,projstr:3,prompt:[11,33,42,46],prone:19,pronounc:28,proper:[5,15,18,20,22,23,30],properli:[1,3,5,7,23],properti:23,proport:9,propos:[29,33,34,35,38],proprietari:39,protect:38,protocol:[32,36,39],prototyp:47,provid:[0,1,3,5,7,8,10,11,12,13,14,15,16,18,20,21,22,23,24,30,33,34,35,36,37,39,40,42,44,47],prudent:15,publicli:39,publish:[24,49],pull:[0,1,13,14,18,23,25,28,32,33,35,36,39],purpl:[9,45],purpos:[10,16,20,21,22,23,32,37,38,50],push:[28,29,30,32,34,36],put:[13,37],pwd:42,pxi:18,py:[16,18,20,21,22,28,30,32,37,42,46],pyao:16,pycharm:40,pydata:[16,35],pyobjecthasht:18,pyplot:[3,9,10,11,13,20,22,45],pyproj:5,pythia:[2,8,14,16,18,20,21,22,23,25,30,32,33,35,36,37,39,41,46,49],pythia_dataset:[18,20,21,22,23],pythia_foundations_env:[41,46],python3:[18,20,21,22],python:[1,2,4,5,6,8,10,11,12,13,14,15,16,17,18,19,21,22,23,25,26,28,30,32,35,37,39,42,44,50],pyx:18,q:[28,33],quadmesh:[9,23],qualit:9,qualiti:[2,11,45],quantit:18,quantiti:5,question:[1,5,8,21,23,38,39],queu:21,quick:[7,15,20,23,28,42,45],quicker:24,quickest:40,quickli:[0,7,13,18,21,24,38,40,42,47,50],quickstart:[7,14,18,27,35,37,40,44],quit:[7,9,16,17,18,21,38,41],quot:[20,21,22,28,45],r11i1p1f1:[20,22],r11i1p1f1grid:[20,22],r11i1p1f1xarrai:[20,22],r1i1p1f1physics_index:[20,22],r:[9,13,18,20,26,42,49],rad2deg:14,radar:[15,16],radian:14,radiat:16,rag:5,rain:5,rais:[10,18,28,42],ram:21,ran:32,randn:[5,13,15,23],random:[5,9,10,12,13,14,15,21,28],randomli:[21,29],rang:[5,7,9,14,15,18,23,42,45],rangeindex:18,rapid:[9,47],rather:[16,21,45],raw:[1,18,19,20,22,23,50],rc:10,rdylgn:11,re:[3,5,7,11,13,15,16,18,24,25,28,33,37,41,42,47],reach:14,read:[1,5,9,16,17,18,23,30,36,37,39,42,45,47,50],read_csv:18,readabl:[9,15,18,36,43],reader:[1,50],readi:[15,24,28,29,30,34,37,38,42],readili:18,readm:[28,35],real:[7,15,20,21,22,30,42,47,50],realiti:7,realli:[15,28,37],realm:[20,21,22],realpython:7,realunit:[20,21,22],reanalysi:23,reason:[8,18,24,28,30,40,45,47],recal:[3,14,23,28,32],receiv:[32,36,37],recent:[3,9,13,14,15,18,20,22,28,29,30,33,34,35,38,39],recip:37,recogn:[15,23,33,42],recommend:[1,5,18,24,28,29,30,32,34,36,37,38,40],record:[20,22,33],red:[9,10,11,13,22,38],redefin:42,redirect:[26,32,37],redisplai:42,reduc:[9,10,15,18,20,30],reduct:[20,21],ref:31,refer:[0,10,16],referenc:[1,2,3,6,16,44],reflect:[21,30,35],reformat:7,refresh:[9,30,42],regardless:[10,18,21,40,42],region:[20,21,23],regist:[32,36],regrid:[20,22],regriddinggrid_label:[20,22],regular:[5,13,30],regularli:[5,14],reinforc:21,reintroduc:14,reject:34,rel:[13,15,16,30],relat:[1,6,7,18,21,23,32,33,36,38,47],relationship:9,relative_humid:13,releas:[16,24,49],relev:[0,1,5,15,20,21,28,30,33,38],remain:[3,23,38],remark:37,rememb:[14,16,37,42,45],remind:[13,28],remot:[23,28,29,32,34,35,36,39,42],remov:[1,3,5,18,20,23,24,28,38],renam:[14,30,32,41,42],render:[1,3,10,18,38,42,50],reopen:42,reorder:42,repeat:[3,7,15,30,37,45],repeatedli:10,replac:[1,5,7,15,20,21,28,38],repo:[5,28,29,32,34,35,36,37,38,39],report:[5,7,20,21,22,33,35,39],repositori:[0,3,20,25,29,33,34,36,38,39,50],repository_url:37,repres:[3,5,7,9,10,13,14,15,18,20,22,23,30,39],represent:[5,7,18,21,45],reproduc:[10,24,44],request:[0,1,20,21,22,25,28,32,33,35,36,39,40],requir:[1,3,5,10,13,14,16,20,21,22,28,29,30,32,34,36,37,38,45,46,50],requisit:5,rerun:[37,42],resampl:20,research:[16,20,22,37],resembl:[5,22,23,30],reshap:[13,14,15],resid:[5,32,35],resiz:42,resolut:[33,38],resolv:[23,30,32,33,49],resourc:[16,33],respect:37,rest:[28,50],restart:1,restor:[28,37],restrict:[3,11],result:[3,11,13,14,15,16,18,20,23,28,41,42,50],retain:[23,28],retriev:[18,20,21,23,28,42],reus:[9,32,42,49],reveal:[14,23],revers:[14,18],revert:[28,46],review:[1,9,15,18,20,23,29,30,34,36,37],reviewnb:38,revis:[28,30,34],revisit:[28,42],rho:1,rich:[21,22,23],right:[1,7,9,15,28,32,33,38,41,42,45,50],rigor:18,river:3,rm:28,rmdir:42,robinson:22,robust:[17,18,20,23,24,38],rock:37,rocket:[45,50],roll:[20,22],room:28,root:[5,42],rose:[28,49],rough:[9,22],roughli:1,round:[7,9,14,15],rout:40,routin:12,row:[1,7,11,13,14,15,16,18,32],rule:[1,14,15,18,24,30],run:[0,1,5,7,8,9,10,15,18,20,21,22,23,24,26,27,28,32,37,44],rundown:40,runnabl:26,runner:[18,20,23],runtim:[7,15,42],ryan:3,s:[8,9,12,16,19,22,25,29,44,45,47,50],safe:[28,30,32,37],safeguard:38,sai:[5,15,28,30,32,36],said:37,sake:5,same:[1,3,5,7,9,10,11,13,14,15,18,20,21,22,24,26,30,32,35,37,39,40,42],sampl:[1,5,7,20,28,30,32,37],sample_environ:24,sandbox:[28,30,32,34,36,37],satisfi:[13,28,37,38],satur:9,save:[3,11,14,16,18,21,28,29,42,46],saw:[7,28,37],scalabl:15,scalar:5,scale:[3,16,21],scale_factor:5,scatter:11,scenario:[9,22,32],scene:23,school:32,scienc:[5,7,11,16,22,50],scientif:[5,6,12,13,14,15,16,20,21,23,28,39,44,45,47,48,50],scientist:[16,26,40,50],scipi:[9,14,16,23],scitool:3,scope:[1,7],scratch:25,screen:[3,32,42],screencast:21,screenshot:[36,38,39,41],script:[3,24,28,32,37,40,42,46],scroll:[16,34,36],sea:[9,20,21,22,29],sea_ic:[20,22],sea_mask:3,sea_surface_temperaturetim:[20,21,22],seaborn:8,search:18,searchabl:33,season:20,second:[3,5,7,9,10,11,14,18,20,22,28,30,32,42,50],section:[0,2,3,4,5,6,7,9,10,11,12,15,16,17,18,19,20,21,23,25,28,29,30,32,34,35,36,37,38,40,42,43,44,50],secur:[18,25],see:[0,3,5,9,10,14,15,16,18,20,21,22,23,24,28,29,30,32,33,34,35,36,37,41,42,45,50],seed:[10,13],seem:3,seemingli:7,seen:[11,28,50],sel:[20,22],select:[1,12,13,14,15,18,20,21,24,32,36,37,38,41,42,50],self:[5,18,23],semi_major_axi:5,semicolon:3,seminar:[26,30,42],send:[1,34],sens:[7,47,50],sensit:42,sent:42,separ:[1,5,10,11,13,15,17,18,21,28,32,37],sequenc:[14,34,37],sequenti:[1,9,18,37],seri:[11,15,16,20,21,22,26,28,30,37,39,42],serializationwarn:[20,21,22],seriou:1,serv:[2,23,36,39,49,50],server:[7,30,41,50],servic:[23,26,38,39],session:[24,40,41,46],set:[3,5,6,7,9,10,11,14,15,18,20,21,22,25,28,32,35,36,37,39,41,46,47],set_data:10,set_ext:22,set_facecolor:3,set_titl:[3,11],set_xlabel:11,set_xlim:10,set_ylabel:11,set_ylim:10,sever:[5,7,16,22,35],sh:28,shade:11,shape:[2,3,5,12,13,14,15,20,21],shapefil:2,share:[3,5,18,20,21,22,26,39,41,42,49,50],sharealik:[20,22],sheet:[9,10,24],shell:[28,32,50],shelv:[20,21,22],shift:[41,42,45,47,50],shine:7,ship:[45,50],shortcom:23,shortcut:[7,18,41],shorten:[11,14,23],shorter:15,shorthand:18,should:[1,2,5,7,9,11,12,13,14,15,16,19,20,21,22,23,30,32,34,37,38,39,41,42,50],show:[1,3,5,7,9,10,14,15,18,21,23,28,29,30,32,33,37,43],shown:[1,5,7,9,10,18,20,21,22,23,28,29,35,36,42],shut:41,shutdown:41,side:[10,42],sidebar:[1,38,41,50],sigma:[1,9],sign:[36,39],signal:37,signific:23,significantli:7,similar:[3,5,9,11,14,18,20,21,23,26,28,32,37,38,40,42],similarli:[10,14,16,18,20,23,24,36],simpl:[5,8,11,16,18,19,20,21,28,29,30,33,37,38,43],simpler:[15,23,37],simplest:[11,28,37,50],simpli:[0,3,5,7,9,15,18,21,23,28,30,33,35,36,37,38,39,45,46,50],simplic:3,simplifi:[14,15,16,20,23,28,37,38],simul:[12,20,22],simultan:[9,18,20,41,42],sin:[3,9,10,13,14,45],sin_t:14,sinc:[0,3,5,10,20,21,22,23,28,29,32,33,42],sine:10,sine_integr:14,sing:28,singl:[1,8,10,11,13,15,18,20,21,22,23,30,34,35,37,40,42,45],sintheta:45,sit:37,site:[5,18,20,21,22,33,42,50],situ:[5,20,21,22],situat:[1,7,38],six:[5,7,22],size:[1,3,5,9,10,11,13,14,15,20,21,22],skill:[16,38,44,48,50],skip:15,slang:28,sleep:[7,42],slice:[15,17,21,22],slightli:37,slope:11,slower:[11,47],small:[3,9,21,28,29,37,38],smaller:[9,18,21,30],smooth:22,snapshot:28,sneak:18,so:[3,5,7,13,14,15,16,18,21,22,23,24,28,29,30,32,35,36,37,39,40,42,47,49],social:36,softwar:[1,5,16,26,28,32,35,37,42,44],solid:[3,16],solut:[15,37],solv:[15,21,26,30],some:[1,5,8,10,11,13,14,15,16,18,20,21,22,23,24,25,29,30,32,33,36,38,39,41,42],some_equ:9,someon:[33,34,47],somepackag:24,someth:[13,14,24,28,32,33,37,42,45],sometim:[5,18,36,42,47,50],sometru:21,somewhat:[13,30],somewher:[13,28],sophist:14,sort:[12,13,18,21,33],sort_valu:18,sound:9,sourc:[0,5,7,8,9,17,18,19,24,25,26,28,32,35,37,40,41,42,44,48,49,50],source_id:[20,22],south:[16,22],southern:18,space:[3,5,14,18,24,34,45],span:9,spatial:[3,5,20,23],spe:50,speci:45,special:[8,9,10,20],specif:[1,5,7,9,10,13,14,18,20,21,23,24,25,26,28,30,33,34,35,37,38,40,44,49],specifi:[5,7,9,10,11,13,14,15,18,20,23,24,37,39],speed2:13,speed:[13,14,15,18,21],spell:38,spend:16,sphere:32,spheric:5,spit:42,split:[21,30],sporad:5,spreadsheet:[17,18,35],sprung:39,spyder:[24,40],sqrt:9,squar:[3,15,20,23,42,45],ss:7,ssh:[39,40],sst:[20,22,29],stack:[7,16,21,23,42,44],stage:[13,32,37],stai:30,stale:30,stand:42,standard:[1,5,6,7,8,16,20,22,23,25,26,38,42,45,50],standard_nam:[5,23],standard_parallel:5,standardbranch_time_in_child:[20,22],start:[1,5,7,9,11,12,13,14,15,16,18,21,23,28,33,34,38,40,41,42,44,45,48,50],stat:9,state:[1,5,7,15,20,21,23,28,38,42],state_bord:3,statement:[1,7,15,18,23,37,38,42,45],statist:[9,12,16,22,42],statu:[29,30,37,42],std:[18,20,21,22,23],std_dev:22,step:[0,1,5,14,18,20,21,22,23,24,28,29,30,34,36,37,38,39,50],stereograph:3,stid:5,stid_var:5,still:[3,5,9,28,30,37,39,42],stock_img:3,stop:[7,14,18,21,23,28],storag:21,store:[3,5,7,13,21,23,28,32,35,37,41,46],storm:7,str:5,str_len:5,straight:[3,40],straightforward:[5,21],strategi:[3,21],stream:21,streamlin:[16,23],strictli:[0,21],stride:15,stride_trick:15,strike_tim:7,string:[3,5,7,9,10,18,42,47],strongli:[20,22],structur:[1,5,9,18,19,23,33,47],struggl:33,studio:[30,40],stupid:28,style:[9,11,14,18],sub:[15,16],sub_experiment_id:[20,22],subhead:42,subject:7,submiss:22,submit:[20,22,29,32,34,37,38],submodul:45,subplot:[3,10,20],subplot_mosa:9,subplot_numb:11,subsequ:[3,5,7,28,37],subset:[13,15,21],subtract:22,subvers:28,succe:[21,37],success:[10,37,39],successfulli:[1,5,34],succinct:15,suddenli:28,suffici:38,suggest:[0,13,15,16,30,33,34,38],suit:[7,11,28,33],sum:[10,13,14,20,21,31,45,47],sum_:42,sumcom:[20,22],summar:[1,18,20],supercel:7,supercomput:[40,46],suppli:[7,20,22],support:[3,5,8,10,16,20,21,26,32,36,37,39,40,50],suppos:7,suppress:3,suptitl:9,sure:[1,9,13,18,21,28,32,33,37,38,50],surfac:[11,20,21,22,23,25,29],surfaceposit:23,surfaceunit:23,surfacexarrai:23,surg:7,surprisingli:37,surround:35,svg:11,svn:39,swap:23,sy:[1,21],symbol:[20,21,23,42],sync:[30,37],synchron:35,syntax:[7,8,9,14,18,20,23,24,40,42],synthet:13,system:[1,3,7,8,20,24,26,28,29,32,36,37,40,41,42,44],t:[5,7,9,13,14,15,16,18,21,23,28,30,36,37,39,42,47,50],tab:[11,13,35,36,38,41,42],tabl:[1,18,20,22,42],table_id:[20,22],tableau:11,tabular:[16,17,18,19,23],tag:[1,10,21,29],tail:18,tailor:19,take:[0,1,3,7,9,10,11,13,14,15,16,18,20,21,23,24,28,29,32,33,34,37,38,39,42,45,47,50],taken:[11,21,22],talk:42,tandem:16,tarea:20,tareadescript:[20,22],target:30,task:[7,16,22,33,37,38],taught:40,tbound:[20,21,22],tbv:5,tcp:39,team:[1,30,34,37,38],teammat:34,technic:[10,21],techniqu:[1,9,11,15,18,21,22,23],technolog:[25,50],teleconnect:18,tell:[15,28,30],temp2:13,temp:[5,11,13,15,23],temp_45:13,temp_in_celsiu:23,temp_slic:5,temp_var:5,temperatur:[5,9,11,13,15,18,20,21,22,23,29],temperature_degc:18,temperature_isobar:23,temperaturegrid_map:23,temperaturemipt:[20,21,22],temperaturetyp:[20,21,22],templat:[0,33],tempor:[5,20,21,22,23],temporarili:15,temps_1000:11,temps_strid:15,temps_var:5,ten_highest:13,tend:47,tenth:13,term:[3,5,7,11,20,21,22,32,39,47],termin:[24,26,30,32,34,38,41,50],termsofus:[20,22],test:[7,20,32,39,40,41,42,46],text:[1,3,5,13,18,26,28,29,32,33,37,38,40,45,46,50],textbook:50,th:10,than:[1,3,7,10,11,14,16,18,20,21,22,35,37,38,45,47,50],thank:1,thankfulli:30,thei:[1,3,5,6,7,9,11,14,15,16,20,21,22,23,24,25,28,30,33,34,37,38,40,42,47,50],them:[1,5,6,7,9,13,14,16,18,20,21,23,28,29,30,32,33,34,35,37,42,45,47,50],theme:42,therebi:10,therefor:[5,7,16,18,20,21,23,35,38],theta:45,thi:[0,2,3,4,5,6,7,9,10,12,13,14,15,16,17,18,19,20,21,22,23,24,25,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47,48],thing:[1,5,8,11,14,16,23,28,37,45],think:[26,28,37],third:[7,11,38,42],thorni:7,thoroughli:30,those:[2,3,5,7,13,14,18,20,21,23,28,32,36,38,39],though:[3,9,15,28,38,39,42],thought:[22,26,33],thousand:21,three:[3,5,7,9,10,14,20,22,28,35,42],through:[1,2,3,7,10,11,12,14,18,19,20,21,23,25,28,29,30,33,34,36,37,38,40,42,45,50],throughout:[15,32],thu:[5,8,9,28,30,39],thumb:30,thunderstorm:7,ti:[23,26],tide_dur:7,tide_length:7,tightli:19,tile:15,tim:5,time1:23,time1pandasindexpandasindex:23,time:[0,1,3,5,6,9,10,11,13,14,15,16,18,20,21,22,24,26,29,30,31,32,33,34,35,36,37,38,39,40,41,42,45,46,50],time_bnd:[20,22],time_bndsstandard_nam:[20,21,22],time_bound:5,time_coordinateaxistyp:23,time_label:[20,21],time_titl:[20,21],time_unit:5,time_v:5,time_var:5,timearrai:23,timedelta:[5,23],timeit:[7,42],timelong_nam:23,timepandasindexpandasindex:[20,21,22,23],timer:42,timescal:7,timeseri:[5,6,18,23],timestamp:[28,35],timetime_label:[20,21,22],timetitl:[20,21,22],timetyp:[20,21,22],timezon:7,tip:[18,38],titl:[1,5,9,10,20,21,22,33,34,37,45],tm:7,to_csv:18,todai:28,todo:33,togeth:[13,14,15,24,30,32,34,37,47],toggl:36,toi:14,toler:23,too:[9,11,13,16,21,37,42],took:7,tool:[5,8,10,15,17,18,24,28,35,37,38,39,40,41,44],toolbar:[41,42],toolbox:8,toolkit:16,toolset:24,top:[0,1,2,8,9,16,17,18,19,20,21,22,23,32,33,36,37,38,41,42,45,50],topic:[1,6,9,13,14,15,18,20,22,23,25,32,33,35,36,38,50],topk:21,torvald:[28,39],tos:[20,21,22,29],tos_anom:20,tos_clim:20,tos_nino34:22,tos_nino34_anom:22,tosarrai:[20,22],toslong_nam:[20,21,22],tosprov:[20,21,22],tosvariant_info:[20,22],tosxarrai:[20,21],total:[1,13,20,21,22,32,45],touch:[30,42,46],tour:45,traceback:[13,14,15,18],track:[5,7,16,21,28,30,33,34,39],tracker:28,tracking_id:[20,22],trade:[15,16],tradit:23,tradition:16,train:[7,23],trajectori:5,transfer:30,transform:[2,3,12,14,20,22],translat:23,transpar:21,transpos:20,trapz:14,tree:28,trial:[1,7],trickier:28,trigger:34,trivial:50,tropic:7,truckload:28,truli:42,tue:28,tunnel:40,tupl:[9,15],turbo:20,ture:1,turn:[13,15,17,24,28,36,42],tutori:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,17,18,19,20,21,22,23,26,29,30,33,35,36,37,38,40,42,43,44,45],twelv:5,two:[5,7,9,13,14,16,18,20,21,22,23,24,28,29,30,32,33,34,36,37,42,45,50],txt:[28,42],tyle:[28,49],type:[1,3,5,7,8,9,10,11,14,15,18,20,21,23,24,29,30,32,33,36,38,39,41,42,46,47],typeerror:18,typic:[5,7,10,22,30,32,34,35,37,38,39,42,47],typo:[1,28,29,33],tzinfo:7,u:[3,13,23],u_wind:23,ucar:[20,22],udunit:5,uk:3,ultim:[30,32,33],unabl:37,unambigu:7,unavail:[10,23],unawar:7,unchang:38,uncheck:36,unclear:33,uncomfort:24,uncommon:9,under:[0,3,4,6,7,16,17,20,22,28,31,34,37,38,41,43,45,49],underestim:1,underli:[1,14],underneath:34,underpin:15,underscor:42,understand:[1,5,7,9,13,14,15,18,20,21,23,28,30,35,38,40],understood:18,undo:28,undoubtedli:16,unfamiliar:[1,18,23,38],unfortun:[7,15,32],unidata:[5,7,23],unintend:42,uniqu:[5,18,24,28,39],unit:[5,7,20,21,23],univers:[7,28],unix:[28,50],unknown:21,unless:37,unlik:[21,32],unlimit:5,unmask:20,unneed:10,unord:9,unsatisfi:37,unsatur:9,unsav:42,unstag:28,unsubscrib:36,unsuccess:7,until:[1,15,21,28,30,37,38,50],untitl:[41,42],untrack:30,unus:5,unwant:10,unwatch:36,unweight:20,unweighted_mean_global_anom:20,up:[5,10,11,14,15,18,20,21,22,23,24,25,26,27,28,29,32,33,34,36,37,39,41,45,46,47,50],updat:[0,5,24,28,37,38,40,42],upload:[32,36,42],upon:[7,11,16,23,28],upper:[11,32,41,42],upsampl:20,upstream:[30,32,34,35],url:[26,29,32,36,37],us:[0,2,4,5,6,7,8,9,14,16,17,19,22,25,27,29,30,32,33,35,36,37,38,39,40,41,42,43,45,46,47,49],usabl:[5,23],usag:[15,18,20,21,23,28,39,45],usainstitution_id:[20,22],user:[5,12,13,14,16,18,20,21,24,25,26,27,28,30,35,36,38,39,42,44,47,50],usernam:30,usr:[18,20,21,22],usual:[1,5,7,9,10,16,18,20,35,38,42,45,47],utc:5,utcnow:[5,7],util:[11,21,47],v2023:49,v2:20,v3:23,v3featuretyp:23,v:[13,23,30,32,37,42],val:15,vala:15,valb:15,valid:[5,13,18,20,21,23,32],valid_max:5,valid_min:5,valu:[3,5,9,10,11,14,15,17,21,22,42,45],valuabl:[13,15],valueerror:[10,14,15],var_7:23,vari:[5,28],variabl:[7,10,11,20,21,22,28,40],variable_id:[20,21,22],variablesmipt:[20,22],variablestyp:[20,22],varianc:[9,20],variant_info:[20,22],variant_label:[20,22],varieti:[3,8,11,14,29,37],variou:[3,7,9,10,11,12,13,18,20,35,39],vast:45,vcss:28,ve:[0,3,10,15,16,28,30,32,34,42,45,46,47,50],vec:13,vector:[2,6,16,20],verbos:14,veri:[1,9,14,15,16,17,18,21,23,28,32,38,40,42,47,50],verif:38,verifi:[5,7,18,22],vernacular:32,versa:33,version:[0,1,3,21,24,25,29,30,32,34,36,37,38,41,42,44,49,50],vertic:[5,20,22,23],via:[0,3,11,18,20,22,24,25,30,32,34,36,37,39,40,42],vice:33,vicin:3,video:[3,10,20,21,35,39],view:[0,1,3,5,9,15,18,21,23,36,37,42,50],viewabl:0,vim:[40,46],viridi:9,virtu:32,visibl:37,visit:[21,39,40],visual:[8,9,11,16,18,19,21,24,26,30,40,47,50],visualis:16,vital:[35,40],vmax:[9,11,22],vmin:[9,11,22],vocabulari:30,volum:[5,20,22],vs:[32,42],w:[5,23],wa:[1,3,7,9,16,18,20,21,22,23,28,30,33,37,42,45,50],wai:[1,5,7,9,10,11,13,14,15,16,18,20,21,23,24,28,29,30,33,34,36,37,39,40,42,45,50],wait:[21,29,30,34,37,42],walk:[15,25],wall:21,want:[7,9,10,11,15,18,23,24,25,29,30,33,34,36,37,40,41,42,46,47,50],ward:16,warm:22,warrant:18,warranti:[20,22],wasn:28,watch:36,water:3,wave:10,we:[1,3,5,7,8,9,10,11,13,14,15,16,18,20,21,22,23,24,25,29,30,32,33,34,35,36,37,39,40,41,42,43,45,46,50],weather:[5,7,16,23],web:[0,26,28,32,37,38,40,41,42,50],webpag:38,websit:[2,19],wedg:10,weight:[20,22],weighted_mean_global_anom:20,well:[1,3,5,7,8,9,13,14,15,17,18,20,21,26,28,29,32,33,35,38,40,42,50],were:[5,8,15,19,21,26,28,30,33,37],what:[25,29,47,50],whatev:[7,32,36],wheel:16,when:[0,1,3,5,6,7,8,9,10,11,13,15,16,18,21,22,23,26,28,29,30,32,33,34,35,36,37,39,40,41,42,47,50],whenev:[3,36],where:[0,1,3,5,6,7,9,10,11,13,14,15,18,21,22,23,28,30,32,34,37,38,39,40,41,42,45,46,50],wherea:42,wherev:[1,28],whet:45,whether:[1,5,9,16,20,32,33,42],which:[1,3,5,7,8,9,10,11,13,14,15,16,18,19,20,21,22,23,24,26,28,29,30,32,33,34,35,36,37,38,39,40,41,42,44,47,50],whirlwind:45,whistl:40,white:[9,42],whiteak:5,who:[35,40],wholeheartedli:38,whom:[28,38],whose:[7,11,26],why:[7,9,11,23,25,33,40,44],wide:[11,14,19,28,37,47],wider:[9,16],widespread:47,width:[9,21],wikipedia:39,wind:[13,16,23],wind_vector:13,windgrid_map:23,window:[15,20,22,37,41,42,46,50],windspharm:16,wise:13,wish:[1,7,21,32,36,37],within:[5,8,13,14,15,16,18,20,23,24,28,30,33,35,37,40,41,42,47],without:[0,1,7,9,10,14,15,18,20,22,23,28,30,32,33,37,40,41],won:[9,15,36,37],wonder:[8,9],word:[5,10,15,20,21,28,30,32,35],worflow:37,work:[0,1,2,3,5,7,9,10,11,13,14,15,16,17,18,19,20,21,23,24,25,26,28,30,32,33,34,35,36,38,40,41,42,44,45,46,47,48,49,50],workflow:[9,20,24,25,28,29,32,38,40,47,50],workhors:[16,23],workspac:[42,46],world:[1,3,14,15,22,41,42,45,46],worri:[13,18,28,47],would:[5,7,11,14,15,16,23,24,30,32,33,34,37,38,42,50],wouldn:47,wq:46,wrap:[9,21],wrapper:[18,23],wrf:5,write:[9,14,15,16,17,23,26,28,32,34,36,37,42,47],writer:10,written:[7,10,14,15,16,28,38,42],wrong:28,wrote:[28,42],www:3,x27:[20,21,22,23],x2:9,x:[1,5,9,10,11,13,14,15,16,18,20,22,23,42,45,46],x_ind:15,x_var:5,xarrai:[2,5,6,7,14,15,16,18,29,35,39],xbound:[20,21,22],xdev:[3,26,30,42],xlabel:[10,18,45],xpandasindexpandasindex:23,xr:[20,21,22],xrai:19,xtick:9,xy:1,xz:1,y2:9,y:[1,5,7,9,10,11,13,14,15,23,42,45],y_ind:15,y_var:5,yaml:0,ybound:[20,21,22],year:[5,18,20,37,39],yearli:18,yellow:[9,10],yet:[3,5,11,21,28,30,37],yield:[7,23],yincreas:23,ylabel:[10,18],yml:[0,50],you:[0,1,2,3,5,7,8,9,10,11,13,14,15,16,18,19,20,23,24,25,26,29,32,33,34,35,36,37,40,41,42,45,46,47,48,49,50],your:[3,5,7,8,9,10,11,13,14,15,16,20,21,24,25,26,27,28,29,32,33,35,37,38,39,40,42,44,46,47,50],your_axi:9,your_fork:37,your_usernam:37,yourself:[3,16,33,38,42,44,45],youtub:21,youtubevideo:21,ypandasindexpandasindex:23,ytick:9,yup:37,z1:11,z2:[9,11],z:[1,5,9,11,15,21],zacharia:49,zenodo:49,zero:[7,9,11,15,27],zeros_lik:15,zip:14,zlib:5,zonal:20},titles:["Pythia Foundations Contributor\u2019s Guide","Project Pythia Notebook Template","Cartopy","Introduction to Cartopy","Data Formats","NetCDF and CF: The Basics","Datetime","Times and Dates in Python","Matplotlib","Annotations, Colorbars, and Advanced Layouts","Histograms, Pie Charts, and Animations","Matplotlib Basics","NumPy","Intermediate NumPy","NumPy Basics","NumPy Broadcasting","Overview","Pandas","Introduction to Pandas","Xarray","Computations and Masks with Xarray","Dask Arrays with Xarray","Calculating ENSO with Xarray","Introduction to Xarray","Installing and Managing Python with Conda","Getting Started with GitHub","Getting Started with Jupyter","Getting Started with Python","Basic Version Control with git","Contribute to Project Pythia via GitHub","Git Branches","Advanced GitHub Topics","Cloning and Forking a Repository","Issues and Discussions","Opening a Pull Request on GitHub","GitHub Repositories","Configuring Your GitHub Account","GitHub Workflows","Reviewing Pull Requests","What is GitHub?","Installing and Running Python","Python in Jupyter","JupyterLab","Formatted Text in the Notebook with Markdown","Overview","Quickstart: Zero to Python","Python in the Terminal","Why Python?","Pythia Foundations","How to Cite This Book","How to Use This Book"],titleterms:{"1":10,"2":[10,23],"2nd":15,"3":[10,22],"4":22,"boolean":13,"class":[7,9,21],"float":45,"function":[10,14,20],"import":[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23],"int":45,"new":[0,1,3,28,30,37],"ni\u00f1o":22,"public":36,"switch":[30,34],"try":[32,35],A:[1,3,7,28,45,48],In:8,No:39,One:23,The:[5,9,18,22,23,30,42],about:28,access:[23,36],account:[36,39],across:28,ad:[9,11],add:[3,9],add_subplot:11,addit:10,advanc:[9,31],aggreg:[20,23],ahead:15,algorithm:21,all:11,along:23,an:[3,10,11,14,36],analysi:18,anim:10,annot:9,anomali:[20,22],anoth:1,appli:[18,20],approxim:23,ar:[21,24,30,33,35],arang:14,area:3,arithmet:[14,20],around:9,arrai:[13,14,21,23,45],assign:23,attempt:23,attribut:23,auxiliari:5,avoid:15,awar:7,ax:[3,11,13],azimuth:3,back:28,background:3,base:48,basic:[3,5,9,11,14,18,28,45],begin:18,behind:15,between:15,bigger:21,binder:50,block:[15,21],book:[0,49,50],bound:5,boundari:11,box:9,branch:[28,30,34,37],broadcast:15,browser:45,build:0,calcul:[7,14,15,21,22],care:28,cartograph:3,cartopi:[2,3],cell:[5,42],center:3,cf:5,chang:[28,29,37],charact:45,chart:10,check:28,choos:40,chunk:21,cite:49,climatolog:20,clone:[32,37],cloud:50,coastal:7,code:[42,45],collect:21,color:11,colorbar:9,colormap:9,column:18,combin:20,command:[24,28,42],commit:[0,28,37],commun:48,compar:28,comparison:13,complet:[23,30],comput:[20,21,22,48],concept:3,conda:[0,24,40],condit:20,configur:36,consider:9,consol:42,constant:14,contain:[21,23],content:[1,31],contour:11,contribut:[0,29],contributor:0,control:[7,11,28,39,45],coordin:[5,23],core:[7,16],creat:[0,3,5,9,11,14,21,23,24,28,29,30,37],creation:[5,23],custom:[9,11,18,20,23],d:23,dai:7,danger:[1,5,18,42],dask:21,data:[3,4,5,7,9,11,14,15,18,20,21,23,45],dataarrai:23,datafram:18,dataset:23,date:[0,7],datetim:[6,7,18],deal:7,delet:30,demonstr:1,deriv:15,dev:0,dice:18,dict:45,dictionari:45,did:[38,39],differ:[3,7,11,15,21],dimens:[5,15,23],discuss:33,displai:11,distribut:[28,35],domain:16,down:42,draft:34,each:11,earth:3,edit:29,editor:42,either:36,elaps:7,email:36,end:18,enso:22,environ:[0,24],equal:3,equat:9,exampl:[1,3,7,35,50],execut:26,exercis:21,exit:41,experi:39,explicit:15,explor:3,exploratori:18,extend:[3,15,18],extrem:18,fanci:3,featur:[3,28,34,37],field:5,figur:[3,11],file:5,fill:[5,11,20],find:15,fine:36,first:[1,21,23,45],flow:45,fork:[30,32,34,37],format:[4,7,43],foss:39,foundat:[0,48,50],free:39,from:[3,45],full:7,funcanim:10,further:1,gener:[11,14,23,36],geoax:3,georeferenc:3,geoscienc:48,get:[18,25,26,27],git:[28,30,37],github:[25,28,29,31,34,35,36,37,39],give:15,global:3,go:28,graphic:45,grid:[5,11],groupbi:20,guid:0,have:[7,30],header:1,high:[16,20,47],higher:15,histogram:10,histori:28,hook:0,how:[1,7,49,50],http:36,hub:26,id:[5,40],iloc:18,imag:[3,11],implicitli:15,index:[13,14,18,22],indic:13,info:[1,3,5,7,11,13,14,18,23,42],inform:5,initi:[10,23],inspect:28,instal:[0,24,40,41,46],integ:45,interact:50,interfac:42,intermedi:13,internet:39,interpol:23,interpret:47,introduc:23,introduct:[3,18,23],investig:18,issu:[23,33],jupyt:[0,26,40,41,50],jupyterlab:42,just:7,keep:0,kei:36,kernel:[26,42],keypair:36,know:[28,38,39],lab:26,label:11,lambert:3,languag:47,last:1,layout:9,lazi:21,learn:[21,48],left:42,level:[1,16,20,47],librari:16,lightn:7,like:23,limit:11,line:11,linspac:14,list:45,littl:28,ll:28,loc:[18,23],local:[0,26,50],log:28,look:[15,18,38],loop:[15,45],lower:3,magic:42,main:[30,37],make:[23,28,29,37],manag:[24,40],mani:7,manipul:21,map:3,markdown:[42,43],mask:20,math:14,matplotlib:[3,8,11,45],max:15,me:36,merg:30,method:[3,7,20,23],min:15,mini:28,mode:42,model:26,modul:7,mollweid:3,more:[21,23],mosaic:[3,9],multidimension:14,multipl:20,naiv:7,name:23,natur:3,navig:34,nearest:23,necessari:39,neighbor:23,neq:39,netcdf:[5,23],next:[1,3,5,7,10,11,13,14,15,18,20,23,24,26,28,30,31,32,33,34,35,36,37,38,39,40,41,42,46],nomenclatur:28,normal:9,note:3,notebook:[0,1,26,40,41,42,43,50],notif:36,now:45,number:45,numpi:[11,12,13,14,15,21,23,45],object:[7,11,21,23,47],observ:5,often:3,one:20,open:[23,34,39,47],open_dataset:23,oper:[18,20,23],organ:50,orient:47,other:[9,20,28,40],our:28,out:28,output:7,over:[3,15],overview:[1,3,5,7,9,10,11,13,14,15,16,18,20,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,44,46],packag:24,page:1,panda:[17,18],parallel:21,pars:7,perform:14,person:36,pie:10,platform:40,plot:[3,11,18,23],point:45,pre:0,preambl:50,predefin:3,prerequisit:[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,46],preserv:23,pressur:23,previou:28,print:7,privat:36,program:45,progress:10,project:[1,3,29],provid:38,pull:[29,30,34,37,38],push:37,put:11,pythia:[0,1,29,48,50],python:[7,24,27,40,41,45,46,47,48],pytz:7,queri:7,quick:[1,18],quickstart:45,random:23,raw:42,read:[7,21],refer:[1,3,5,7,9,11,13,14,15,18,20,21,22,23,24,26,28,30,31,32,33,34,35,36,37,38,39,40,41,42,45,46],region:[3,22],regist:39,remot:[26,30,37],render:9,replac:13,repositori:[28,30,32,35,37],request:[29,30,34,37,38],resampl:18,resolut:3,resourc:[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23,24,26,30,38,40,41,42,45,46,48],restart:42,result:21,review:38,room:15,run:[40,41,42,45,46,50],s:[0,1,3,5,7,10,11,13,14,15,18,20,21,23,24,26,28,30,31,32,33,34,35,36,37,38,39,40,41,42,46],safeti:37,same:23,sampl:[9,23],satellit:3,save:[10,41],scatterplot:11,second:1,section:[1,8,31],secur:36,see:7,sel:23,select:[22,23],seri:18,set:[1,23,30,42],set_ext:3,set_xlim:11,set_ylim:11,setup:[5,20],shapefil:3,share:[9,11,23],sharei:11,sharex:11,shell:42,shortcom:21,shortcut:42,should:28,shut:42,sidebar:42,simpl:[7,23],sinc:7,site:0,slice:[13,14,18,23],softwar:39,some:[3,7,9,28,35,45],sourc:[39,47],space:23,spam:36,special:42,specif:16,specifi:[3,21],split:20,ssh:36,stage:28,stai:39,start:[25,26,27,37],state:[3,10],station:5,statist:18,statu:28,step:10,stop:[36,42],str:45,strftime:7,strike:7,string:45,strptime:7,structur:21,subplot:[9,11],subsect:1,subset:[14,18,23],success:1,suggest:29,summari:[1,3,5,7,9,10,11,13,14,15,18,20,21,22,23,24,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,46],support:7,synchron:37,system:[5,39],templat:1,termin:[40,42,46],terminolog:7,test:[11,21],text:[9,42,43],thi:[1,8,11,28,45,49,50],thing:[32,35],tide:7,time:[7,23,28],timedelta:7,timestamp:7,tip:[35,36,37],titl:11,togeth:11,token:36,topic:[27,31,44],tune:39,tutori:[16,28],two:[3,11],type:45,unix:7,up:[0,1,30,42],updat:30,upstream:37,us:[1,3,10,11,13,15,18,20,21,23,24,28,50],usag:7,user:32,utc:7,valu:[13,18,20,23],variabl:[5,23,42],vc:[28,39],vector:15,veri:45,version:[28,39],versu:7,via:[29,50],view:[28,38],visual:[22,23],vs:36,wai:38,want:28,warn:[1,3,13,14,18,42],we:28,web:39,what:[1,3,5,7,10,11,13,14,15,18,20,21,23,24,26,28,30,31,32,33,34,35,36,37,38,39,40,41,42,45,46],when:38,where:20,why:[8,21,28,47],window:32,within:11,work:37,workflow:[30,34,37],would:21,wrap:23,write:[5,7],xarrai:[19,20,21,22,23],xr:23,year:7,york:3,you:[21,28,30,38,39],your:[0,1,18,30,34,36,41,45],zero:45,zone:7}}) \ No newline at end of file