{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Week 06: Continuing interactivity in Python with the viz engine bqplot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## But first, some hints for the HW" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at an example dataset:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "bf = pd.read_csv(\"https://raw.githubusercontent.com/UIUC-iSchool-DataViz/is445_bcubcg_fall2022/main/data/bfro_reports_fall2022.csv\",\n", " parse_dates = [\"date\"])\n", "\n", "# you might get a memory warning thing, its just not deprecated correctly\n", "# try not to panic :D" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
observedlocation_detailscountystateseasontitlelatitudelongitudedatenumber...precip_intensityprecip_probabilityprecip_typepressuresummaryuv_indexvisibilitywind_bearingwind_speedlocation
0Ed L. was salmon fishing with a companion in P...East side of Prince William SoundValdez-Chitina-Whittier CountyAlaskaFallNaNNaNNaNNaT1261.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1heh i kinda feel a little dumb that im reporti...the road is off us rt 80, i dont know the exit...Warren CountyNew JerseyFallNaNNaNNaNNaT438.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2I was on my way to Claremont from Lebanon on R...Close to Claremont down 120 not far from Kings...Sullivan CountyNew HampshireSummerReport 55269: Dawn sighting at Stevens Brook o...43.41549-72.330932016-06-0755269.0...0.0010.7rain998.87Mostly cloudy throughout the day.6.09.70262.00.49POINT(-72.33093000000001 43.415490000000005)
3I was northeast of Macy Nebraska along the Mis...Latitude & Longitude : 42.158230 -96.344197Thurston CountyNebraskaSpringReport 59757: Possible daylight sighting of a ...42.15685-96.342032018-05-2559757.0...0.0000.0NaN1008.07Partly cloudy in the morning.10.08.25193.03.33POINT(-96.34203000000001 42.15685)
4While this incident occurred a long time ago, ...Ward County, Just outside of a the Minuteman T...Ward CountyNorth DakotaSpringReport 751: Hunter describes described being s...48.25422-101.316602000-04-21751.0...NaNNaNrain1011.47Partly cloudy until evening.6.010.00237.011.14POINT(-101.3166 48.254220000000004)
..................................................................
4742My cousin and I were camping way out in the wo...Indiana, Brown County, Elkinsville, Lake Monro...Brown CountyIndianaSpringNaNNaNNaNNaT2460.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
4743While backpacking near the horse trails and ac...Near Bedford south of Brown County in the Hoos...Brown CountyIndianaWinterNaNNaNNaNNaT2461.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
4744My wife and I were camping At Yellowood State ...Yellowood State Park. Off of highway 46 in bet...Brown CountyIndianaSummerReport 49480: Campers hear possible vocalizati...39.17909-86.335602015-08-0849480.0...0.0000.0NaN1014.02Mostly cloudy in the evening.9.09.22256.00.34POINT(-86.3356 39.17909)
4745My wife and I were driving to Indianapolis to ...On Interstate 65 in Indiana somewhere around t...Boone CountyIndianaWinterNaNNaNNaNNaT2459.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
4746It was about 7:00 PM in September. It was stil...Blackford County, Indiana located in the south...Blackford CountyIndianaSummerNaNNaNNaNNaT2458.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", "

4747 rows × 29 columns

\n", "
" ], "text/plain": [ " observed \\\n", "0 Ed L. was salmon fishing with a companion in P... \n", "1 heh i kinda feel a little dumb that im reporti... \n", "2 I was on my way to Claremont from Lebanon on R... \n", "3 I was northeast of Macy Nebraska along the Mis... \n", "4 While this incident occurred a long time ago, ... \n", "... ... \n", "4742 My cousin and I were camping way out in the wo... \n", "4743 While backpacking near the horse trails and ac... \n", "4744 My wife and I were camping At Yellowood State ... \n", "4745 My wife and I were driving to Indianapolis to ... \n", "4746 It was about 7:00 PM in September. It was stil... \n", "\n", " location_details \\\n", "0 East side of Prince William Sound \n", "1 the road is off us rt 80, i dont know the exit... \n", "2 Close to Claremont down 120 not far from Kings... \n", "3 Latitude & Longitude : 42.158230 -96.344197 \n", "4 Ward County, Just outside of a the Minuteman T... \n", "... ... \n", "4742 Indiana, Brown County, Elkinsville, Lake Monro... \n", "4743 Near Bedford south of Brown County in the Hoos... \n", "4744 Yellowood State Park. Off of highway 46 in bet... \n", "4745 On Interstate 65 in Indiana somewhere around t... \n", "4746 Blackford County, Indiana located in the south... \n", "\n", " county state season \\\n", "0 Valdez-Chitina-Whittier County Alaska Fall \n", "1 Warren County New Jersey Fall \n", "2 Sullivan County New Hampshire Summer \n", "3 Thurston County Nebraska Spring \n", "4 Ward County North Dakota Spring \n", "... ... ... ... \n", "4742 Brown County Indiana Spring \n", "4743 Brown County Indiana Winter \n", "4744 Brown County Indiana Summer \n", "4745 Boone County Indiana Winter \n", "4746 Blackford County Indiana Summer \n", "\n", " title latitude longitude \\\n", "0 NaN NaN NaN \n", "1 NaN NaN NaN \n", "2 Report 55269: Dawn sighting at Stevens Brook o... 43.41549 -72.33093 \n", "3 Report 59757: Possible daylight sighting of a ... 42.15685 -96.34203 \n", "4 Report 751: Hunter describes described being s... 48.25422 -101.31660 \n", "... ... ... ... \n", "4742 NaN NaN NaN \n", "4743 NaN NaN NaN \n", "4744 Report 49480: Campers hear possible vocalizati... 39.17909 -86.33560 \n", "4745 NaN NaN NaN \n", "4746 NaN NaN NaN \n", "\n", " date number ... precip_intensity precip_probability \\\n", "0 NaT 1261.0 ... NaN NaN \n", "1 NaT 438.0 ... NaN NaN \n", "2 2016-06-07 55269.0 ... 0.001 0.7 \n", "3 2018-05-25 59757.0 ... 0.000 0.0 \n", "4 2000-04-21 751.0 ... NaN NaN \n", "... ... ... ... ... ... \n", "4742 NaT 2460.0 ... NaN NaN \n", "4743 NaT 2461.0 ... NaN NaN \n", "4744 2015-08-08 49480.0 ... 0.000 0.0 \n", "4745 NaT 2459.0 ... NaN NaN \n", "4746 NaT 2458.0 ... NaN NaN \n", "\n", " precip_type pressure summary uv_index \\\n", "0 NaN NaN NaN NaN \n", "1 NaN NaN NaN NaN \n", "2 rain 998.87 Mostly cloudy throughout the day. 6.0 \n", "3 NaN 1008.07 Partly cloudy in the morning. 10.0 \n", "4 rain 1011.47 Partly cloudy until evening. 6.0 \n", "... ... ... ... ... \n", "4742 NaN NaN NaN NaN \n", "4743 NaN NaN NaN NaN \n", "4744 NaN 1014.02 Mostly cloudy in the evening. 9.0 \n", "4745 NaN NaN NaN NaN \n", "4746 NaN NaN NaN NaN \n", "\n", " visibility wind_bearing wind_speed \\\n", "0 NaN NaN NaN \n", "1 NaN NaN NaN \n", "2 9.70 262.0 0.49 \n", "3 8.25 193.0 3.33 \n", "4 10.00 237.0 11.14 \n", "... ... ... ... \n", "4742 NaN NaN NaN \n", "4743 NaN NaN NaN \n", "4744 9.22 256.0 0.34 \n", "4745 NaN NaN NaN \n", "4746 NaN NaN NaN \n", "\n", " location \n", "0 NaN \n", "1 NaN \n", "2 POINT(-72.33093000000001 43.415490000000005) \n", "3 POINT(-96.34203000000001 42.15685) \n", "4 POINT(-101.3166 48.254220000000004) \n", "... ... \n", "4742 NaN \n", "4743 NaN \n", "4744 POINT(-86.3356 39.17909) \n", "4745 NaN \n", "4746 NaN \n", "\n", "[4747 rows x 29 columns]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Second thing to notice, is this is a pretty darn big dataset! This can get a little cumbersome if you want to be able to do some quick prototyping of visualizations. \n", "\n", "Depending on what sort of computer you are working on, you may want to consider working with a *random subset* of this dataframe for your \"prototyping\" phase, and then only use the full dataset when you are ready for *production*:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([4492, 345, 2967, 239, 3029, 4407, 3753, 3049, 3476, 3302, 592,\n", " 23, 118, 2961, 937, 1676, 3323, 1904, 1339, 3122, 2957, 1805,\n", " 1899, 1885, 4327, 4150, 556, 292, 2675, 4307, 15, 4713, 1884,\n", " 2974, 3482, 3141, 3930, 944, 4554, 1069, 313, 1299, 4047, 3441,\n", " 3418, 1909, 4223, 1905, 1912, 467, 3600, 4135, 1210, 4052, 1522,\n", " 1932, 75, 1538, 3442, 3037, 4370, 1416, 894, 4526, 2555, 656,\n", " 1008, 1820, 3342, 3870, 3774, 2446, 1442, 1407, 994, 1446, 1095,\n", " 1390, 245, 424, 2960, 2519, 1135, 4555, 3121, 4037, 267, 2251,\n", " 1735, 4233, 304, 3767, 3919, 3166, 1370, 2354, 2474, 3691, 4514,\n", " 1466, 3995, 837, 674, 2039, 4094, 1217, 1014, 4566, 85, 1894,\n", " 4465, 157, 4665, 1220, 2467, 4287, 178, 65, 2979, 1880, 4129,\n", " 2732, 4108, 2317, 1839, 582, 2930, 1243, 4161, 1150, 1928, 2226,\n", " 1410, 2548, 2725, 2431, 4567, 608, 3004, 448, 4373, 4376, 3276,\n", " 3428, 3701, 4261, 1553, 679, 499, 4081, 851, 3073, 123, 3567,\n", " 1767, 2000, 399, 1565, 911, 1272, 1526, 3312, 3864, 2145, 101,\n", " 3169, 1937, 3717, 3496, 498, 2828, 4600, 1991, 4363, 1467, 1244,\n", " 2902, 1005, 4158, 3626, 4215, 4340, 1642, 489, 821, 3445, 386,\n", " 3553, 3253, 2756, 2357, 2526, 2856, 2956, 4226, 4679, 2992, 1224,\n", " 1226, 3067, 1219, 3269, 3432, 1970, 451, 1790, 1441, 3437, 4537,\n", " 3739, 3182, 1019, 3268, 1315, 2846, 2129, 200, 727, 1661, 2288,\n", " 2105, 4197, 1451, 465, 1375, 480, 1633, 2735, 2676, 1067, 4545,\n", " 1901, 4653, 2314, 4262, 3271, 2119, 4013, 20, 2041, 2406, 585,\n", " 2067, 3462, 1070, 1333, 2710, 4659, 984, 3897, 1329, 2566, 3781,\n", " 1447, 2031, 4339, 32, 1857, 506, 678, 1146, 1155, 887, 536,\n", " 1987, 651, 1943, 2112, 2597, 2258, 4384, 581, 2609, 2089, 1843,\n", " 701, 2678, 4433, 4073, 3861, 1253, 3361, 1132, 1232, 2790, 797,\n", " 3934, 1137, 3752, 2460, 1741, 2893, 2389, 1604, 1470, 507, 363,\n", " 1161, 1539, 1816, 3706, 1626, 3605, 4229, 2786, 4016, 3393, 2510,\n", " 1638, 2686, 1708, 4123, 4202, 1898, 445, 2858, 4239, 979, 258,\n", " 2820, 2587, 244, 1263, 4618, 4259, 1332, 2188, 289, 2491, 4313,\n", " 1795, 1782, 4133, 589, 1239, 120, 1876, 554, 1910, 4070, 916,\n", " 804, 2652, 3297, 3507, 3069, 1992, 4408, 2823, 2550, 312, 4663,\n", " 3976, 18, 1364, 3679, 2755, 1603, 3663, 3609, 3120, 3745, 1409,\n", " 4515, 2593, 3771, 4217, 3528, 4436, 3280, 2283, 3305, 1460, 4424,\n", " 1045, 3833, 1945, 3422, 2418, 3504, 4715, 4322, 2360, 493, 611,\n", " 2151, 3980, 3123, 3079, 50, 3406, 269, 113, 999, 1055, 1160,\n", " 2356, 4039, 2451, 3335, 2470, 3646, 3525, 337, 2252, 3633, 37,\n", " 1316, 883, 4131, 858, 2772, 4282, 2602, 1960, 2417, 1776, 718,\n", " 3988, 61, 4076, 4736, 3710, 2767, 4668, 746, 1529, 1149, 3652,\n", " 933, 652, 51, 834, 3587, 1413, 3795, 4372, 1303, 4609, 1957,\n", " 4306, 2501, 2878, 1158, 2040, 484, 3892, 1029, 1182, 4200, 3684,\n", " 517, 278, 4662, 1425, 4573, 2399, 1422, 349, 4649, 4331, 3589,\n", " 2614, 206, 1517, 3412, 2452, 1965, 1397, 3417, 4647, 3850, 4493,\n", " 2879, 3257, 2443, 4552, 2409, 1193, 4134, 2121, 3018, 2864, 1766,\n", " 3603, 3031, 1896, 1853, 1748, 2184, 1615, 1122, 1341, 1917, 2641,\n", " 2681, 2742, 787, 4285, 3791])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Lets down sample to 500 samples:\n", "import numpy as np\n", "\n", "nsamples = 500\n", "downSampleMask = np.random.choice(range(len(bf)-1), nsamples, replace=False)\n", "downSampleMask # take a look" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: I haven't set the \"seed\" for this random pull of indicies so your list might be different!\n", "\n", "So, downSampleMask is now a list of random indicies for the UFO dataset. Let's downsample our dataset:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "bfDS = bf.loc[downSampleMask]" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
observedlocation_detailscountystateseasontitlelatitudelongitudedatenumber...precip_intensityprecip_probabilityprecip_typepressuresummaryuv_indexvisibilitywind_bearingwind_speedlocation
4492In November 1986 I was on my bike (bmx). We ju...Two miles south of Hennepin on Hennepin Farms Rd.Putnam CountyIllinoisFallReport 27374: Man remembers his daytime sighti...41.22850-89.321101986-11-2927374.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNPOINT(-89.3211 41.2285)
345i was driving north on Hwy 73 past west Olson ...Approximately 2 1/2 miles south of where MN Hw...St. Louis CountyMinnesotaSpringReport 59346: Daytime road crossing on MN-73 n...47.83334-92.927082018-07-1759346.0...0.00000.0NaNNaNPartly cloudy starting in the afternoon, conti...7.010.00294.00.72POINT(-92.92708 47.83334)
2967In the early 90's growing up in eastern ky my ...Vicinity of a place called Kimper.Pike CountyKentuckyWinterReport 4624: Two teens find prints in the snow...37.49993-82.353001992-12-254624.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNPOINT(-82.353 37.49993)
239I was 8 years old, and very much a nature buff...In Livingston County, MI, near Brighton, appro...Livingston CountyMichiganSummerNaNNaNNaNNaT806.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
3029I saw something along Interstate 75 South in K...30-40 miles south of Cincinnati, OH on Interst...Grant CountyKentuckyFallReport 8715: Motorist has nighttime sighting o...38.52800-84.588002005-02-028715.0...0.00191.0snow1026.43Mixed precipitation starting in the afternoon,...3.06.2755.02.60POINT(-84.588 38.528)
..................................................................
2681My Marine buddy and I were on a hiking/camping...N. of Naval Submarine Base Bangor.Kitsap CountyWashingtonSummerReport 1393: Sighting by two hikers47.76130-122.706301988-06-251393.0...0.00000.0NaN1014.07Mostly cloudy throughout the day.5.010.00187.07.33POINT(-122.7063 47.7613)
2742My mom seen a creature she could not explain o...Route 141 North into Marinette county, to Pemb...Marinette CountyWisconsinFallReport 51419: Daylight sighting by a hunter on...45.64585-88.089602014-11-1951419.0...0.00000.0NaN1007.21Foggy until afternoon.1.06.62277.01.80POINT(-88.0896 45.64585)
787I was camping with my family and went frog c...It's a big Campground with lots of animals. Th...Sussex CountyNew JerseySummerReport 48988: Possible daylight sighting by a ...41.27412-74.641892016-03-0548988.0...0.00000.0NaN1021.69Mostly cloudy starting in the afternoon.5.010.009.00.38POINT(-74.64189 41.27412)
4285About two years ago I was riding my bicycle wi...Take 156 out of Calhoun going west take a righ...Floyd CountyGeorgiaSummerReport 22813: Cyclist hears loud wood knocks i...34.48260-85.102992005-08-1522813.0...0.00000.0NaN1017.78Humid throughout the day.10.07.27353.00.98POINT(-85.10299 34.4826)
3791Creature was near a pond, initially bent over ...NaNPark CountyColoradoSpringNaNNaNNaNNaT1370.0...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", "

500 rows × 29 columns

\n", "
" ], "text/plain": [ " observed \\\n", "4492 In November 1986 I was on my bike (bmx). We ju... \n", "345 i was driving north on Hwy 73 past west Olson ... \n", "2967 In the early 90's growing up in eastern ky my ... \n", "239 I was 8 years old, and very much a nature buff... \n", "3029 I saw something along Interstate 75 South in K... \n", "... ... \n", "2681 My Marine buddy and I were on a hiking/camping... \n", "2742 My mom seen a creature she could not explain o... \n", "787 I was camping with my family and went frog c... \n", "4285 About two years ago I was riding my bicycle wi... \n", "3791 Creature was near a pond, initially bent over ... \n", "\n", " location_details county \\\n", "4492 Two miles south of Hennepin on Hennepin Farms Rd. Putnam County \n", "345 Approximately 2 1/2 miles south of where MN Hw... St. Louis County \n", "2967 Vicinity of a place called Kimper. Pike County \n", "239 In Livingston County, MI, near Brighton, appro... Livingston County \n", "3029 30-40 miles south of Cincinnati, OH on Interst... Grant County \n", "... ... ... \n", "2681 N. of Naval Submarine Base Bangor. Kitsap County \n", "2742 Route 141 North into Marinette county, to Pemb... Marinette County \n", "787 It's a big Campground with lots of animals. Th... Sussex County \n", "4285 Take 156 out of Calhoun going west take a righ... Floyd County \n", "3791 NaN Park County \n", "\n", " state season title \\\n", "4492 Illinois Fall Report 27374: Man remembers his daytime sighti... \n", "345 Minnesota Spring Report 59346: Daytime road crossing on MN-73 n... \n", "2967 Kentucky Winter Report 4624: Two teens find prints in the snow... \n", "239 Michigan Summer NaN \n", "3029 Kentucky Fall Report 8715: Motorist has nighttime sighting o... \n", "... ... ... ... \n", "2681 Washington Summer Report 1393: Sighting by two hikers \n", "2742 Wisconsin Fall Report 51419: Daylight sighting by a hunter on... \n", "787 New Jersey Summer Report 48988: Possible daylight sighting by a ... \n", "4285 Georgia Summer Report 22813: Cyclist hears loud wood knocks i... \n", "3791 Colorado Spring NaN \n", "\n", " latitude longitude date number ... precip_intensity \\\n", "4492 41.22850 -89.32110 1986-11-29 27374.0 ... NaN \n", "345 47.83334 -92.92708 2018-07-17 59346.0 ... 0.0000 \n", "2967 37.49993 -82.35300 1992-12-25 4624.0 ... NaN \n", "239 NaN NaN NaT 806.0 ... NaN \n", "3029 38.52800 -84.58800 2005-02-02 8715.0 ... 0.0019 \n", "... ... ... ... ... ... ... \n", "2681 47.76130 -122.70630 1988-06-25 1393.0 ... 0.0000 \n", "2742 45.64585 -88.08960 2014-11-19 51419.0 ... 0.0000 \n", "787 41.27412 -74.64189 2016-03-05 48988.0 ... 0.0000 \n", "4285 34.48260 -85.10299 2005-08-15 22813.0 ... 0.0000 \n", "3791 NaN NaN NaT 1370.0 ... NaN \n", "\n", " precip_probability precip_type pressure \\\n", "4492 NaN NaN NaN \n", "345 0.0 NaN NaN \n", "2967 NaN NaN NaN \n", "239 NaN NaN NaN \n", "3029 1.0 snow 1026.43 \n", "... ... ... ... \n", "2681 0.0 NaN 1014.07 \n", "2742 0.0 NaN 1007.21 \n", "787 0.0 NaN 1021.69 \n", "4285 0.0 NaN 1017.78 \n", "3791 NaN NaN NaN \n", "\n", " summary uv_index visibility \\\n", "4492 NaN NaN NaN \n", "345 Partly cloudy starting in the afternoon, conti... 7.0 10.00 \n", "2967 NaN NaN NaN \n", "239 NaN NaN NaN \n", "3029 Mixed precipitation starting in the afternoon,... 3.0 6.27 \n", "... ... ... ... \n", "2681 Mostly cloudy throughout the day. 5.0 10.00 \n", "2742 Foggy until afternoon. 1.0 6.62 \n", "787 Mostly cloudy starting in the afternoon. 5.0 10.00 \n", "4285 Humid throughout the day. 10.0 7.27 \n", "3791 NaN NaN NaN \n", "\n", " wind_bearing wind_speed location \n", "4492 NaN NaN POINT(-89.3211 41.2285) \n", "345 294.0 0.72 POINT(-92.92708 47.83334) \n", "2967 NaN NaN POINT(-82.353 37.49993) \n", "239 NaN NaN NaN \n", "3029 55.0 2.60 POINT(-84.588 38.528) \n", "... ... ... ... \n", "2681 187.0 7.33 POINT(-122.7063 47.7613) \n", "2742 277.0 1.80 POINT(-88.0896 45.64585) \n", "787 9.0 0.38 POINT(-74.64189 41.27412) \n", "4285 353.0 0.98 POINT(-85.10299 34.4826) \n", "3791 NaN NaN NaN \n", "\n", "[500 rows x 29 columns]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bfDS" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "image/png": "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a+G0C6ny+sPD8ZOMRBysaMDopGgGHkRE5DIGH6TasAQGHURE5D7N0y7V1dV4/PHH0b17d4SFhWH48OEoLi423y+KIpYsWYL4+HjodDrcddddOHHihEcPmoiIiDovTcFHQ0MDJkyYgFtuuQWbN2/G119/jddffx1RUVHmbV577TW88cYbePvtt1FUVIS4uDjcc889uHz5sqePnYiIiDohQRRF0flmJrm5udizZw927dole78oioiPj8fzzz+PRYsWAQBaW1vRq1cvLFu2DPPmzXP6HE1NTdDr9TAajYiMjFR7aERERORHWq7fmkY+/vGPf2D06NF46KGH0LNnT4wYMQIrV640319eXo7a2lrce++95ttCQ0MxceJEFBYWyu6ztbUVTU1NVj9ERETUdWkKPs6ePYt33nkH/fv3x9atW/Hzn/8czz33HD766CMAQG1tLQCgV69eVo/r1auX+T5b+fn50Ov15p+EhARXXgcRERF1EpqCj/b2dowcORJLly7FiBEjMG/ePMydOxfvvPOO1XaCYN0JTBRFu9skeXl5MBqN5p+qqiqNL4GIiIg6E03Bh8FgwODBg61uGzRoECorKwEAcXFxAGA3ylFXV2c3GiIJDQ1FZGSk1Q8RERF1XZqCjwkTJuDUqVNWt50+fRqJiYkAgOTkZMTFxeGLL74w39/W1oYdO3YgMzPTA4dLREREnZ2mImP/8R//gczMTCxduhQPP/wwDhw4gPfeew/vvfceANN0y/PPP4+lS5eif//+6N+/P5YuXYqwsDDMmTPHKy+AiIiIOhdNwceYMWPwySefIC8vD7/97W+RnJyMN998E4899ph5mxdffBEtLS2YP38+GhoaMG7cOHz++eeIiIjw+METERFR56OpzocvsM4HERFR5+O1Oh9ERERE7mLwQURERD7F4IOIiIh8isEHERER+RSDDyIiIvIpBh9ERETkUww+iIiIyKcYfBAREZFPMfggIiIin2Lw0UnVGFtQWFaPGmOLvw+FiIhIE029XahjWFtUibz1JWgXgSAByJ+Zhtlj+vr7sIiIiFThyEcnU2NsMQceANAuAovXH+cICBERdRoMPjqZ8vpmc+AhuS6KqKi/6p8DIiIi0ojBRyeTHBuOIMH6tmBBQFJsmH8OiIiISCMGH52MQa9D/sw0BAumCCRYELB05lAY9Do/HxkREZE6TDjt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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# take a quick look\n", "import matplotlib.pyplot as plt\n", "plt.clf() # might need this to \"get rid\" of Michigan data\n", "plt.plot(bf['longitude'],bf['latitude'],'.')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.clf() # might need this to \"get rid\" of Michigan data\n", "plt.plot(bfDS['longitude'],bfDS['latitude'],'.')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can try with a scatter plot as well which allows us to add in another encoding with color:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Index(['observed', 'location_details', 'county', 'state', 'season', 'title',\n", " 'latitude', 'longitude', 'date', 'number', 'classification', 'geohash',\n", " 'temperature_high', 'temperature_mid', 'temperature_low', 'dew_point',\n", " 'humidity', 'cloud_cover', 'moon_phase', 'precip_intensity',\n", " 'precip_probability', 'precip_type', 'pressure', 'summary', 'uv_index',\n", " 'visibility', 'wind_bearing', 'wind_speed', 'location'],\n", " dtype='object')" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bf.columns" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "image/png": "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/e957bXX+nySAwcOMG7cOJ566imuv/76bvs/3vLheR5HjhyhpKQEY3pbdDkzNDU1UVNTw549e8jPzx/p6ox6imdqKZ6ppXimluKZWqmIp7WW5uZmqqqqcJy+u5QO6bLL1772NZ599lleeeWVfrObMWPGMG7cOLZt29bj/mAw2K1FpLCwcCjVGrXy8/P1x5NCimdqKZ6ppXimluKZWicbz4KCgS0UOKjkw1rL1772NX7961+zatUqJkyY0O9j6uvr2bNnD2PGjBnMU4mIiEiGGtRQ2zvuuIMnnniCJ598kry8PGpra6mtraW9vR2AlpYWvvWtb7F69Wp27tzJqlWrWLJkCaWlpVx33XXD8gJERERkdBlUy8fPfvYzAC655JKk7cuXL+eWW27BdV02bdrE448/ztGjRxkzZgyXXnopTz/9NHl5eSmrdKYIBoN8//vf77Ejrgye4plaimdqKZ6ppXimVrrjOeQOpyIiIiJDobVdREREJK2UfIiIiEhaKfkQERGRtFLyISIiImml5CMNli1bxoIFC8jOzu5xArW3336bG264gZqaGrKyspg2bRoPPvhgt3KbNm1i4cKFZGVlMXbsWO69994BzaGfafqLJ8A3vvENzjvvPILBIHPmzOmxjOIZN5B47t69myVLlpCTk0NpaSlf//rXiUQiSWUUz9699dZbLF68mMLCQkpKSvirv/orWlpaksoMJMYS98EHH7B06VJKS0vJz8/nwgsv5KWXXkoqo3gOzKpVqzDG9Hhbu3Ztolyq46nkIw0ikQif/exnue2223rcv379esrKynjiiSfYvHkz3/3ud/nOd77Dww8/nCjT1NTE4sWLqaqqYu3atTz00EPcf//9PPDAA+l6GaeM/uIJ8QnxvvjFL/KXf/mXPe5XPI/rL56xWIyrrrqK1tZWXnvtNZ566il+9atf8c1vfjNRRvHs3f79+/nUpz7FpEmTeOONN1ixYgWbN2/mlltuSZQZSIzluKuuuopoNMqLL77I+vXrmTNnDldffTW1tbWA4jkYCxYs4MCBA0m3L3/5y4wfP565c+cCwxRPK2mzfPlyW1BQMKCyt99+u7300ksT9x955BFbUFBgOzo6Etvuu+8+W1VVZT3PS3VVR4WBxPP73/++nT17drftimd3vcXzueees47j2H379iW2/fu//7sNBoO2sbHRWqt49uXRRx+15eXlNhaLJbZt2LDBAnbbtm3W2oHFWOIOHTpkAfvKK68ktjU1NVnAvvDCC9ZaxfNkRCIRW15ebu+9997EtuGIp1o+TlGNjY0UFxcn7q9evZqFCxcmTQBz+eWXs3//fnbu3DkCNRzdFM+BW716NTNnzqSqqiqx7fLLLyccDrN+/fpEGcWzZ+FwmEAgkLTQVlZWFgCvvfYaMLAYS1xJSQnTpk3j8ccfp7W1lWg0yqOPPkpFRQXnnXceoHiejGeffZbDhw8ntcwNRzyVfJyCVq9ezS9+8Qu++tWvJrbV1tZSUVGRVO7Y/WNNjTJwiufA9RSroqIiAoFAIlaKZ+8WLVpEbW0tP/7xj4lEIjQ0NHDXXXcB8VW/YWAxljhjDCtXrmTDhg3k5eURCoX4yU9+wooVKxJ9lhTPofv5z3/O5ZdfTk1NTWLbcMRTyccQ3X333b120jl2W7du3aCPu3nzZpYuXcr3vvc9Fi9enLTPGJN033Z15vv49tFouOLZF8Vz4PHsKSbW2qTtmRzPngw0xjNmzOCxxx7jH//xH8nOzqayspIzzzyTiooKXNdNHG8gMc5kA42ntZbbb7+d8vJyXn31Vd58802WLl3K1VdfnUjmQPEcymfA3r17+eMf/8iXvvSlbsdLdTwHtbaLHHfnnXfyuc99rs8y48ePH9Qxt2zZwqJFi/jKV77C3/3d3yXtq6ys7JZh1tXVAXTLSEej4YhnXxTPgcezsrKSN954I2lbQ0MDnZ2diVhlejx7MpgY33jjjdx4440cPHiQnJwcjDE88MADiZXBBxLjTDfQeL744ov87ne/o6GhIbH0+yOPPMLKlSt57LHH+Pa3v614MrTPgOXLl1NSUsI111yTtH044qnkY4hKS0spLS1N2fE2b97MokWLuPnmm1m2bFm3/fPnz+euu+4iEokQCAQAeP7556mqqkrpSXmkpDqe/VE8B27+/PksW7aMAwcOMGbMGCAeq2AwmLjGnunx7MlQYnzsg/pf//VfCYVCidbNgcQ40w00nm1tbQBJfWiO3fc8D1A8YfC/n9Zali9fzhe+8AX8fn/SvmGJ51B7xMrA7dq1y27YsMHec889Njc3127YsMFu2LDBNjc3W2utfffdd21ZWZn9/Oc/bw8cOJC41dXVJY5x9OhRW1FRYW+44Qa7adMm+8wzz9j8/Hx7//33j9TLGjH9xdNaa7dt22Y3bNhgv/rVr9opU6YkyoTDYWut4nmi/uIZjUbtzJkz7Sc/+Un71ltv2RdeeMFWV1fbO++8M3EMxbNvDz30kF2/fr3dunWrffjhh21WVpZ98MEHE/sHEmOJO3TokC0pKbHXX3+93bhxo926dav91re+Zf1+v924caO1VvEcihdeeMECdsuWLd32DUc8lXykwc0332yBbreXXnrJWhsfDtrT/nHjxiUd55133rGf+MQnbDAYtJWVlfbuu+8+LYcx9hdPa61duHBhj2V27NiRKKN4xg0knrt27bJXXXWVzcrKssXFxfbOO+9MGlZrreLZl5tuuskWFxfbQCBgZ82aZR9//PFuZQYSY4lbu3atveyyy2xxcbHNy8uz8+bNs88991xSGcVzcG644Qa7YMGCXvenOp7GWk1BKCIiIumj0S4iIiKSVko+REREJK2UfIiIiEhaKfkQERGRtFLyISIiImml5ENERETSSsmHiIiIpJWSDxEREUkrJR8iIiKSVko+REREJK2UfIiIiEhaKfkQERGRtPr/rC1xC/c2PocAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.clf()\n", "plt.scatter(bfDS['longitude'],bfDS['latitude'], \n", " c=np.log10(bfDS['pressure']), cmap='rainbow')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hint:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAABj0AAANCCAYAAAA9Sw4jAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA7MklEQVR4nO3df5SWdZ34/9cNjjczNojCOsPoJHhiyySzlTJhC7IYt7DcPLollnryV0vuRmiuqOmgCUlFbLGU7m5G27K657SedUsFikQ9sIkkWdTJbQ+CvybKWCDBYWSu7x99uL8Mg8qNM87wmsfjHI57v+9rrnnfO+d+MTfPrvsuFUVRBAAAAAAAwEFuUF9vAAAAAAAAoCeIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAEBERHzrW9+KUqkUTzzxRF9vJVpbW6NUKnVZW7hwYXzrW9/qle83adKkmDRpUq+cGwAAeO2IHgAAQL9z8cUXx6pVq7qs9Wb0AAAAcjikrzcAAACwt2OOOSaOOeaYvt4GAABwkHGlBwAA8JK++c1vxlvf+tYYMmRIHHnkkfHhD384fvnLX3Y55sILL4zXve518etf/zo+8IEPxOte97pobm6OK664Itrb27sc+9RTT8XZZ58d9fX1MWzYsDjvvPNi9erVUSqVulzFsffbW40aNSrWrVsXK1asiFKpFKVSKUaNGhURL/22XPfff3+USqW4//77K2tFUcTcuXPj2GOPjSFDhsSf/dmfxb333rvPx75169a48sorY/To0XHooYfG0UcfHdOnT4/nn3+++v9HAgAArwlXegAAAPs0Z86cuOaaa+Lcc8+NOXPmxHPPPRetra1x6qmnxurVq2PMmDGVYzs6OuJDH/pQXHTRRXHFFVfEAw88EDfddFMcfvjhcf3110dExPPPPx/vec974ve//33ccsst8YY3vCHuu++++MhHPvKKe7nrrrvi7LPPjsMPPzwWLlwYERHlcrnqxzRr1qyYNWtWXHTRRXH22WfHk08+GZdcckns2rUr3vjGN1aO2759e0ycODGeeuqpuOaaa+LEE0+MdevWxfXXXx8/+9nP4gc/+EG3zxwBAAD6nugBAAB083//939x0003xQc+8IFYvHhxZX3SpEkxZsyYaG1tjX/913+trO/cuTNmzZoV55xzTkREvPe9741HHnkkFi9eXIkeixYtil//+tdx7733xl/8xV9ERERLS0ts3749br311pfdz9ve9raora2NoUOHxjvf+c4Dfky33HJLfPjDH45/+qd/qqyfcMIJMWHChC7R46tf/Wo89thj8eMf/zjGjRtXeUxHH310nH322XHffffF+9///gPaBwAA0Hu8vRUAANDNqlWrYseOHXHhhRd2WW9ubo7TTjstfvjDH3ZZL5VK8cEPfrDL2oknnhgbNmyo3F6xYkXU19dXgsdu5557bs9u/iWsWrUqXnjhhTjvvPO6rI8fPz6OPfbYLmvf+973YuzYsXHSSSfFiy++WPlz+umnd3vLLAAAoP8QPQAAgG6ee+65iIgYOXJkt/uampoq9+9WV1cXQ4YM6bJWLpfjhRde6HLOhoaGbufb11pv2L3nxsbGbvftvfab3/wmHnvssaipqenyp76+PoqiiN/97nevyZ4BAIDqeHsrAACgm+HDh0dExLPPPtvtvmeeeSZGjBhxQOd8+OGHu623tbVVv8E97I4te39o+t5hYvdj2tf3a2trq3wwekTEiBEjora2Nr75zW/u83seyOMHAAB6nys9AACAbk499dSora2N73znO13Wn3rqqVi+fHm8973vrfqcEydOjG3btsW9997bZf2OO+7Yr68vl8uxY8eObuu7Y8Vjjz3WZf3uu+/ucvud73xnDBkypMtnkURErFy5ssvbcEVEnHHGGfG///u/MXz48Bg3bly3P3sGEgAAoP9wpQcAANDNsGHD4nOf+1xcc801cf7558e5554bzz33XMyaNSuGDBkSN9xwQ9XnvOCCC+IrX/lKfOxjH4vPf/7z8YY3vCHuvffeWLJkSUREDBr08v+brLe85S1xxx13xJ133hnHHXdcDBkyJN7ylrfE29/+9njjG98YV155Zbz44otxxBFHxF133RUPPfRQl68/4ogj4sorr4zPf/7zcfHFF8c555wTTz75ZLS2tnZ7e6vp06fHd7/73Xj3u98dn/nMZ+LEE0+Mzs7O2LhxYyxdujSuuOKKOOWUU6r+/wEAANC7RA8AAGCfZs6cGUcddVR89atfjTvvvDNqa2tj0qRJMXv27BgzZkzV5zvssMNi+fLlMX369LjqqquiVCpFS0tLLFy4MD7w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AAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugBAAAAAACkIHoAAAAAAAApiB4AAAAAAEAKogcAAAAAAJCC6AEAAAAAAKQgegAAAAAAACmIHgAAAAAAQAqiBwAAAAAAkILoAQAAAAAApCB6AAAAAAAAKYgeAAAAAABACqIHAAAAAACQgugB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AAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEhB9AAAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdED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+1p86Utfinnz5r1WDwPoIa80EyIiiqKIT3ziE/GRj3xkn/ebCZDLK82FXbt2xZQpU+L555+Phx56KO6444747ne/G1dccUXlGHMBcnrmmWfife97X7zhDW+IH//4x3HffffFunXr4sILL6wcsz8zAshlypQp8eKLL8by5ctjzZo1cdJJJ8UZZ5wRbW1tEWEuwEAyfvz4ePbZZ7v8ufjii2PUqFExbty4iBhAM6EAXnO33357cfjhh+/XsdOmTSve8573VG4vXLiwOPzww4sXXnihsjZnzpyiqamp6Ozs7OmtAq+B/ZkJN9xwQ/HWt76127qZADm91Fy45557ikGDBhVPP/10Ze3f/u3finK5XGzZsqUoCnMBsrr11luLo446qti1a1dl7dFHHy0iovif//mfoij2b0YAefz2t78tIqJ44IEHKmtbt24tIqL4wQ9+UBSFuQAD2c6dO4ujjjqquPHGGytrA2UmuNID+rktW7bEkUceWbm9atWqmDhxYpTL5cra6aefHs8880w88cQTfbBDoC+ZCTCwrFq1KsaOHRtNTU2VtdNPPz3a29tjzZo1lWPMBcinvb09Dj300Bg06P9/GV9bWxsREQ899FBE7N+MAPIYPnx4HH/88fHtb387nn/++XjxxRfj1ltvjYaGhjj55JMjwlyAgezuu++O3/3ud12uCh0oM0H0gH5s1apV8e///u9x2WWXVdba2tqioaGhy3G7b+++fBUYOMwEGFj29Zw/4ogj4tBDD608580FyOm0006Ltra2+OIXvxg7d+6MzZs3xzXXXBMREc8++2xE7N+MAPIolUqxbNmyePTRR6O+vj6GDBkSX/nKV+K+++6rfDaYuQAD1z//8z/H6aefHs3NzZW1gTITRA94lVpbW1/yQ4J2/3nkkUeqPu+6devizDPPjOuvvz4mT57c5b5SqdTldvH/Pph073XgtddbM+HlmAnQv/X0XNjXc7soii7r5gIcPPZ3RpxwwgmxaNGi+PKXvxx1dXXR2NgYxx13XDQ0NMTgwYMr59ufGQH0b/s7F4qiiGnTpsVRRx0VDz74YDz88MNx5plnxhlnnFGJoRHmAhzsDuT1xFNPPRVLliyJiy66qNv5BsJMOKSvNwAHu8svvzw++tGPvuwxo0aNquqcv/jFL+K0006LSy65JK677rou9zU2NnYrr5s2bYqI6FZqgddeb8yEl2MmQP/Xk3OhsbExfvzjH3dZ27x5c3R0dFSe8+YCHFyqmRFTp06NqVOnxm9+85s47LDDolQqxbx582L06NERsX8zAuj/9ncuLF++PL73ve/F5s2bY+jQoRERsXDhwli2bFksWrQorr76anMBEjiQ1xO33357DB8+PD70oQ91WR8oM0H0gFdpxIgRMWLEiB4737p16+K0006LCy64IG6++eZu95966qlxzTXXxM6dO+PQQw+NiIilS5dGU1NTj/5DKnBgenomvBIzAfq/npwLp556atx8883x7LPPxsiRIyPij8/5crlcee9ucwEOLgcyI3b/o8Q3v/nNGDJkSOXK8P2ZEUD/t79zYfv27RERXT7rZ/ftzs7OiDAXIINqf1coiiJuv/32OP/886OmpqbLfQNlJnh7K3gNbdy4MdauXRsbN26MXbt2xdq1a2Pt2rXxhz/8ISL+GDze8573xOTJk2PGjBnR1tYWbW1t8dvf/rZyjqlTp0a5XI4LL7wwfv7zn8ddd90Vs2fPjhkzZqS6DA0GgleaCRERv/71r2Pt2rXR1tYWO3bsqByzc+fOiDATIJtXmgstLS3x5je/OT7+8Y/Ho48+Gj/84Q/jyiuvjEsuuaTyv/A0FyCvBQsWxE9+8pN4/PHH4x/+4R/i8ssvjzlz5lTeu39/ZgSQx6mnnhpHHHFEXHDBBfHTn/40Hn/88fjsZz8b69evjylTpkSEuQAD0fLly2P9+vX7fGurATMTCuA1c8EFFxQR0e3Pj370o6IoiuKGG27Y5/3HHntsl/M89thjxbve9a6iXC4XjY2NRWtra9HZ2fnaPyDgVXmlmVAURTFx4sR9HrN+/frKMWYC5LE/c2HDhg3FlClTitra2uLII48sLr/88uKFF17och5zAXL6+Mc/Xhx55JHFoYceWpx44onFt7/97W7H7M+MAPJYvXp10dLSUhx55JFFfX198c53vrO45557uhxjLsDAcu655xbjx49/yfsHwkwoFcX/+1RDAAAAAACAg5i3twIAAAAAAFIQPQAAAAAAgBREDwAAAAAAIAXRAwAAAAAASEH0AAAAAAAAUhA9AAAAAACAFEQPAAAAAAAgBdEDAAAAAABIQfQAAAAAAABSED0AAAAAAIAURA8AAAAAACAF0QMAAAAAAEjh/wNL5Tg/R9m1IAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.clf()\n", "fig, ax = plt.subplots(figsize=(20,10))\n", "bfDS.hist(column='longitude', ax=ax)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### A bit with traitlets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember our color-picker that we linked:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "import ipywidgets" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "6754dbe2ac744c4abd5b52e51ad5857c", "version_major": 2, "version_minor": 0 }, "text/plain": [ "IntSlider(value=0, max=10, orientation='vertical')" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "islider = ipywidgets.IntSlider(min = 0, max = 10, step = 1, orientation = 'vertical')\n", "islider" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0a7bddb3b5834d039aacb9885b274c7b", "version_major": 2, "version_minor": 0 }, "text/plain": [ "ColorPicker(value='black')" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "cp = ipywidgets.ColorPicker()\n", "cp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's link them using .link this time:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ipywidgets.link( (cp, 'value'), (islider.style, 'handle_color') )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We note what prints out now is a \"traitlets.traitlets\" link -- this is part of the python-backend traitlets library." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Traitlets are a way to link the *change in a variable* to an action." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll start by making a class w/o using traitlets. We've worked with classes before, just sort of \"under the radar\" like with Pandas objects. Now we will define our own:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "class MyObject():\n", " name = 'unnamed'\n", " age = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's define a new variable of this `class` type:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "my_obj = MyObject()" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('unnamed', 0)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_obj.name, my_obj.age" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can update the values of these variables like we would any other object:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "my_obj.name = 'Bob'; my_obj.age = 47" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's check out our new values:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, my name is: Bob and I am 47 years old\n" ] } ], "source": [ "print('Hello, my name is:', my_obj.name, 'and I am', my_obj.age, 'years old')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's say I wanted to re-set the values of the variables in my class -- to print out the above statement, I'd have to redo everything:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "my_obj.name = 'Linda'; my_obj.age = 45" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, my name is: Linda and I am 45 years old\n" ] } ], "source": [ "print('Hello, my name is:', my_obj.name, 'and I am', my_obj.age, 'years old')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `traitlets` library allows us to \"track\" changes so that we can associate actions with updates to variables. Let's try this example again:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "import traitlets" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "class MyTraitletObject(traitlets.HasTraits):\n", " # lets use the unicode trait to let our object have a name\n", " name = traitlets.Unicode(\"unnamed\") \n", " # lets also give our object an age\n", " age = traitlets.Int(0)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "my_t_obj = MyTraitletObject()\n", "my_t_obj.age = 47\n", "my_t_obj.name = 'Bob'" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, my name is: Bob and I am 47 years old\n" ] } ], "source": [ "print('Hello, my name is:', my_t_obj.name, 'and I am', my_t_obj.age, 'years old')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far this is not very exciting - we are back where we started! However, there are options to \"observe\" changes in our traits:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create a function that can act on one of the variables of our variables, in particluar, its going to react to the name in my object:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "def name_changed(change):\n", " print(change) # this is a format that is required of a traitlets class\n", "\n", "# lets tell traitlets that we are going to change something\n", "my_t_obj.observe(name_changed, ['name'])" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'name': 'name', 'old': 'Bob', 'new': 'Linda', 'owner': <__main__.MyTraitletObject object at 0x14d0703d0>, 'type': 'change'}\n" ] } ], "source": [ "my_t_obj.name = 'Linda'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that there are these weird \"new\" and \"old\" names -- this is tracking how things change. The \"owner\" is just pointing to the memory location of `my_t_obj`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's do something a little more exciting - have a default print-out of the introduction:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "def name_changed2(change):\n", " print('Hello, my name is', change['new'], 'and I am', change['owner'].age, 'years old')\n", "\n", "# lets tell traitlets that we are going to change something\n", "my_t_obj.observe(name_changed2, ['name'])" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "my_t_obj.name = 'Linda'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that now, I've got *2* things associated with any changes -- let's take off the first one:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "my_t_obj.unobserve(name_changed, ['name'])" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, my name is Bob and I am 47 years old\n" ] } ], "source": [ "my_t_obj.name = \"Bob\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can practice by making a \"watching for a change\" function for age as well:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "def age_changed(change):\n", " print('Hello, my name is', change['owner'].name, 'and I am', change['new'], 'years old')\n", "\n", "my_t_obj.observe(age_changed, ['age'])" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, my name is Bob and I am 48 years old\n" ] } ], "source": [ "my_t_obj.age = 48" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, my name is Louise and I am 48 years old\n", "Hello, my name is Louise and I am 8 years old\n" ] } ], "source": [ "my_t_obj.name, my_t_obj.age = 'Louise', 8" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## On to bqplot!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Make sure you have installed this via the install script `test_imports_week01.ipynb` in week 1. You may have to restart your jupyter or your browswer.\n", "\n", "We'll start by using `bqplot` in a Grammar of Graphics & *Declaritive* sort of way, and then, if we have time, spend a few moments looking at its `matplotlib`-like interface as well which is an *Imperative* method." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "import bqplot\n", "import numpy as np\n", "import ipywidgets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we are going to mess around with some of the declaritive programming type options that bqplot can use. This will rely heavily on the \"Grammar of Graphics\" constructs." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Random line plot with Pan/Zoom interaction\n", "\n", "We'll now go through the example I put up on the slides for this week. \n", "\n", "**Data:** Let's first start by creating *data* elements for our graphic just some random numbers:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "x = np.arange(100) # integers 0->999\n", "y = np.random.random(100) + 5 # random numbers with mean = 5" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]),\n", " array([5.73425149, 5.80983891, 5.27554492, 5.23895409, 5.43313527,\n", " 5.57511084, 5.73067353, 5.30355545, 5.51089579, 5.40806799,\n", " 5.7123796 , 5.70655363, 5.473186 , 5.54126398, 5.0488656 ,\n", " 5.43670384, 5.53166163, 5.28556204, 5.09944372, 5.17681518,\n", " 5.92606774, 5.61356733, 5.57888278, 5.32122873, 5.23287757,\n", " 5.12570239, 5.29231104, 5.06498775, 5.76008418, 5.48461537,\n", " 5.72892289, 5.9302526 , 5.28285599, 5.9945069 , 5.46932167,\n", " 5.59172845, 5.27106905, 5.74707967, 5.99197659, 5.29900954,\n", " 5.71542945, 5.82035838, 5.89369794, 5.14695868, 5.46727033,\n", " 5.73910092, 5.47869716, 5.89124486, 5.50440709, 5.59798109,\n", " 5.13785038, 5.42740366, 5.03984039, 5.40614289, 5.0919698 ,\n", " 5.47465852, 5.04923808, 5.20832803, 5.45473766, 5.46281928,\n", " 5.00059368, 5.1937316 , 5.94875268, 5.99050208, 5.99523084,\n", " 5.13243625, 5.05458502, 5.66869239, 5.07718993, 5.23069819,\n", " 5.09614386, 5.36901663, 5.34056975, 5.95185259, 5.67536906,\n", " 5.93417413, 5.08930133, 5.62192899, 5.62448328, 5.03847078,\n", " 5.05165803, 5.61498854, 5.92023984, 5.18036307, 5.07105078,\n", " 5.18404747, 5.71742767, 5.87159664, 5.72188518, 5.70330991,\n", " 5.85381108, 5.79686439, 5.66250191, 5.0699655 , 5.6080649 ,\n", " 5.6875134 , 5.87570858, 5.58776703, 5.37444731, 5.77083242]))" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x, y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Scales:** Now we'll define some scale objects which will determine how lines will be drawn on our canvas:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLinearScale\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "A linear scale.\n", "\n", "An affine mapping from a numerical domain to a numerical range.\n", "\n", "Attributes\n", "----------\n", "min: float or None (default: None)\n", " if not None, min is the minimal value of the domain\n", "max: float or None (default: None)\n", " if not None, max is the maximal value of the domain\n", "rtype: string (class-level attribute)\n", " This attribute should not be modified. The range type of a linear\n", " scale is numerical.\n", "dtype: type (class-level attribute)\n", " the associated data type / domain type\n", "precedence: int (class-level attribute, default_value=2)\n", " attribute used to determine which scale takes precedence in cases when\n", " two or more scales have the same rtype and dtype.\n", " default_value is 2 because for the same range and domain types,\n", " LinearScale should take precedence.\n", "stabilized: bool (default: False)\n", " if set to False, the domain of the scale is tied to the data range\n", " if set to True, the domain of the scale is updated only when\n", " the data range is beyond certain thresholds, given by the attributes\n", " mid_range and min_range.\n", "mid_range: float (default: 0.8)\n", " Proportion of the range that is spanned initially.\n", " Used only if stabilized is True.\n", "min_range: float (default: 0.6)\n", " Minimum proportion of the range that should be spanned by the data.\n", " If the data span falls beneath that level, the scale is reset.\n", " min_range must be <= mid_range.\n", " Used only if stabilized is True.\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/scales.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.LinearScale?" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "x_sc = bqplot.LinearScale()\n", "y_sc = bqplot.LinearScale()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Marks:** Now we are going to use GoG type calls to define what lines to actually draw combining information about our data and our scales:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLines\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "Lines mark.\n", "\n", "In the case of the Lines mark, scales for 'x' and 'y' MUST be provided.\n", "\n", "Attributes\n", "----------\n", "icon: string (class-level attribute)\n", " Font-awesome icon for the respective mark\n", "name: string (class-level attribute)\n", " User-friendly name of the mark\n", "colors: list of colors (default: CATEGORY10)\n", " List of colors of the Lines. If the list is shorter than the number\n", " of lines, the colors are reused.\n", "close_path: bool (default: False)\n", " Whether to close the paths or not.\n", "fill: {'none', 'bottom', 'top', 'inside', 'between'}\n", " Fill in the area defined by the curves\n", "fill_colors: list of colors (default: [])\n", " Fill colors for the areas. Defaults to stroke-colors when no\n", " color provided\n", "opacities: list of floats (default: [])\n", " Opacity for the lines and patches. Defaults to 1 when the list is too\n", " short, or the element of the list is set to None.\n", "fill_opacities: list of floats (default: [])\n", " Opacity for the areas. Defaults to 1 when the list is too\n", " short, or the element of the list is set to None.\n", "stroke_width: float (default: 2)\n", " Stroke width of the Lines\n", "labels_visibility: {'none', 'label'}\n", " Visibility of the curve labels\n", "curves_subset: list of integers or None (default: [])\n", " If set to None, all the lines are displayed. Otherwise, only the items\n", " in the list will have full opacity, while others will be faded.\n", "line_style: {'solid', 'dashed', 'dotted', 'dash_dotted'}\n", " Line style.\n", "interpolation: {'linear', 'basis', 'cardinal', 'monotone'}\n", " Interpolation scheme used for interpolation between the data points\n", " provided. Please refer to the svg interpolate documentation for details\n", " about the different interpolation schemes.\n", "marker: {'circle', 'cross', 'diamond', 'square', 'triangle-down', 'triangle-up', 'arrow', 'rectangle', 'ellipse'}\n", " Marker shape\n", "marker_size: nonnegative int (default: 64)\n", " Default marker size in pixels\n", "\n", "Data Attributes\n", "\n", "x: numpy.ndarray (default: [])\n", " abscissas of the data points (1d or 2d array)\n", "y: numpy.ndarray (default: [])\n", " ordinates of the data points (1d or 2d array)\n", "color: numpy.ndarray (default: None)\n", " colors of the different lines based on data. If it is [], then the\n", " colors from the colors attribute are used. Each line has a single color\n", " and if the size of colors is less than the number of lines, the\n", " remaining lines are given the default colors.\n", "\n", "Notes\n", "-----\n", "The fields which can be passed to the default tooltip are:\n", " name: label of the line\n", " index: index of the line being hovered on\n", " color: data attribute for the color of the line\n", "The following are the events which can trigger interactions:\n", " click: left click of the mouse\n", " hover: mouse-over an element\n", "The following are the interactions which can be linked to the above events:\n", " tooltip: display tooltip\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/marks.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.Lines?" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "lines = bqplot.Lines(x = x, y = y, scales = {'x': x_sc, 'y': y_sc})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Axis:** Now, we are going to define what axis we want placed around the lines that we draw we'll draw both x & y axis." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAxis\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "A line axis.\n", "\n", "A line axis is the visual representation of a numerical or date scale.\n", "\n", "Attributes\n", "----------\n", "icon: string (class-level attribute)\n", " The font-awesome icon name for this object.\n", "axis_types: dict (class-level attribute)\n", " A registry of existing axis types.\n", "orientation: {'horizontal', 'vertical'}\n", " The orientation of the axis, either vertical or horizontal\n", "side: {'bottom', 'top', 'left', 'right'} or None (default: None)\n", " The side of the axis, either bottom, top, left or right.\n", "label: string (default: '')\n", " The axis label\n", "tick_format: string or None (default: '')\n", " The tick format for the axis, for dates use d3 string formatting.\n", "scale: Scale\n", " The scale represented by the axis\n", "num_ticks: int or None (default: None)\n", " If tick_values is None, number of ticks\n", "tick_values: numpy.ndarray or None (default: None)\n", " Tick values for the axis\n", "offset: dict (default: {})\n", " Contains a scale and a value {'scale': scale or None,\n", " 'value': value of the offset}\n", " If offset['scale'] is None, the corresponding figure scale is used\n", " instead.\n", "label_location: {'middle', 'start', 'end'}\n", " The location of the label along the axis, one of 'start', 'end' or\n", " 'middle'\n", "label_color: Color or None (default: None)\n", " The color of the axis label\n", "grid_lines: {'none', 'solid', 'dashed'}\n", " The display of the grid lines\n", "grid_color: Color or None (default: None)\n", " The color of the grid lines\n", "color: Color or None (default: None)\n", " The color of the line\n", "label_offset: string or None (default: None)\n", " Label displacement from the axis line. Units allowed are 'em', 'px'\n", " and 'ex'. Positive values are away from the figure and negative\n", " values are towards the figure with respect to the axis line.\n", "visible: bool (default: True)\n", " A visibility toggle for the axis\n", "tick_style: Dict (default: {})\n", " Dictionary containing the CSS-style of the text for the ticks.\n", " For example: font-size of the text can be changed by passing\n", " `{'font-size': 14}`\n", "tick_rotate: int (default: 0)\n", " Degrees to rotate tick labels by.\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/axes.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m ColorAxis" ] } ], "source": [ "bqplot.Axis?" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "ax_x = bqplot.Axis(scale = x_sc, label = 'X Value')\n", "ax_y = bqplot.Axis(scale = y_sc, label = 'Y Value', orientation = 'vertical')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Figure:** Finally, we combine all these things together into a bonified figure:" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFigure\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "Main canvas for drawing a chart.\n", "\n", "The Figure object holds the list of Marks and Axes. It also holds an\n", "optional Interaction object that is responsible for figure-level mouse\n", "interactions, the \"interaction layer\".\n", "\n", "Besides, the Figure object has two reference scales, for positioning items\n", "in an absolute fashion in the figure canvas.\n", "\n", "Attributes\n", "----------\n", "title: string (default: '')\n", " title of the figure\n", "axes: List of Axes (default: [])\n", " list containing the instances of the axes for the figure\n", "marks: List of Marks (default: [])\n", " list containing the marks which are to be appended to the figure\n", "interaction: Interaction or None (default: None)\n", " optional interaction layer for the figure\n", "scale_x: Scale\n", " Scale representing the x values of the figure\n", "scale_y: Scale\n", " Scale representing the y values of the figure\n", "padding_x: Float (default: 0.0)\n", " Padding to be applied in the horizontal direction of the figure\n", " around the data points, proportion of the horizontal length\n", "padding_y: Float (default: 0.025)\n", " Padding to be applied in the vertical direction of the figure\n", " around the data points, proportion of the vertical length\n", "legend_location: {'top-right', 'top', 'top-left', 'left',\n", " 'bottom-left', 'bottom', 'bottom-right', 'right'}\n", " location of the legend relative to the center of the figure\n", "background_style: Dict (default: {})\n", " CSS style to be applied to the background of the figure\n", "legend_style: Dict (default: {})\n", " CSS style to be applied to the SVG legend e.g, {'fill': 'white'}\n", "legend_text: Dict (default: {})\n", " CSS style to be applied to the legend text e.g., {'font-size': 20}\n", "title_style: Dict (default: {})\n", " CSS style to be applied to the title of the figure\n", "animation_duration: nonnegative int (default: 0)\n", " Duration of transition on change of data attributes, in milliseconds.\n", "pixel_ratio:\n", " Pixel ratio of the WebGL canvas (2 on retina screens). Set to 1 for better performance,\n", " but less crisp edges. If set to None it will use the browser's window.devicePixelRatio.\n", "\n", "Layout Attributes\n", "\n", "fig_margin: dict (default: {top=60, bottom=60, left=60, right=60})\n", " Dictionary containing the top, bottom, left and right margins. The user\n", " is responsible for making sure that the width and height are greater\n", " than the sum of the margins.\n", "min_aspect_ratio: float\n", " minimum width / height ratio of the figure\n", "max_aspect_ratio: float\n", " maximum width / height ratio of the figure\n", "\n", "Methods\n", "-------\n", "\n", "save_png:\n", " Saves the figure as a PNG file\n", "save_svg:\n", " Saves the figure as an SVG file\n", "\n", "Note\n", "----\n", "\n", "The aspect ratios stand for width / height ratios.\n", "\n", " - If the available space is within bounds in terms of min and max aspect\n", " ratio, we use the entire available space.\n", " - If the available space is too oblong horizontally, we use the client\n", " height and the width that corresponds max_aspect_ratio (maximize width\n", " under the constraints).\n", " - If the available space is too oblong vertically, we use the client width\n", " and the height that corresponds to min_aspect_ratio (maximize height\n", " under the constraint).\n", " This corresponds to maximizing the area under the constraints.\n", "\n", "Default min and max aspect ratio are both equal to 16 / 9.\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/figure.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.Figure?" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "97465898edca4f86945605ce8e8ecf1c", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='X Value', scale=LinearScale(), side='bottom'), Axis(label='Y Value', orientation='ver…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = bqplot.Figure(marks = [lines], axes = [ax_x, ax_y])\n", "#display(fig) # you might also want to try \"display\"\n", "# if you don't see the following fig, here is where\n", "# you might have to close and reopen your notebook\n", "fig # note: just \"fig\" instead of \"display(fig)\" may also be an option for you" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok, but this isn't interactive in anyway lets make it!! There are a few \"interactions\" supported in `bqplot` but not all of them are supported for all types of plots. The docs can be a little nebulous about what plot can use what type of interaction, so we'll just try a few and see what happens. Here, let's add in an ability to pan/zoom in our plot:" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mType:\u001b[0m module\n", "\u001b[0;31mString form:\u001b[0m \n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/interacts.py\n", "\u001b[0;31mDocstring:\u001b[0m \n", "=========\n", "Interacts\n", "=========\n", "\n", ".. currentmodule:: bqplot.interacts\n", "\n", ".. autosummary::\n", " :toctree: _generate/\n", "\n", " BrushIntervalSelector\n", " BrushSelector\n", " HandDraw\n", " IndexSelector\n", " FastIntervalSelector\n", " MultiSelector\n", " OneDSelector\n", " Interaction\n", " PanZoom\n", " Selector\n", " TwoDSelector" ] } ], "source": [ "bqplot.interacts?" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minteracts\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPanZoom\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "An interaction to pan and zoom wrt scales.\n", "\n", "Attributes\n", "----------\n", "allow_pan: bool (default: True)\n", " Toggle the ability to pan.\n", "allow_zoom: bool (default: True)\n", " Toggle the ability to zoom.\n", "scales: Dictionary of lists of Scales (default: {})\n", " Dictionary with keys such as 'x' and 'y' and values being the scales in\n", " the corresponding direction (dimensions) which should be panned or\n", " zoomed.\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/interacts.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.interacts.PanZoom?" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "pz = bqplot.interacts.PanZoom( scales = {'x': [x_sc], 'y': [y_sc]})" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d101ec190ac248a1be843c05d2353608", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='X Value', scale=LinearScale(), side='bottom'), Axis(label='Y Value', orientation='ver…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = bqplot.Figure(marks = [lines], axes = [ax_x, ax_y], interaction = pz)\n", "#display(fig)\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that if I pan and zoom, the figure updates. Ooooo. fancy.\n", "\n", "Note also, that the above figure also reacts as well this is because we are using the same lines & ax's objects -- recall back to last week that this was a feature of using ipywidgets and traitlets as well." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ASIDE: where things can go wrong (SKIPPING generally):" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "# lets see an example of where this can fail\n", "\n", "# first lets make an x from 0-10 in 100 steps\n", "x = np.mgrid[0.0:10.0:100j]\n", "# and 2 y variables\n", "y1 = x * 2\n", "y2 = x**2" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [], "source": [ "x_sc = bqplot.LinearScale(min = 1, max = 10)\n", "# lets do one y-scale over linear and 1 over log\n", "y_sc1 = bqplot.LinearScale(min = 1, max = 20)\n", "y_sc2 = bqplot.LogScale(min = 1, max = 100)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "# lets genrate lines for each y value\n", "lines1 = bqplot.Lines(x = x, y = y1, scales = {'x': x_sc, 'y': y_sc1})\n", "lines2 = bqplot.Lines(x = x, y = y2, scales = {'x': x_sc, 'y': y_sc2})" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "# and lets plot an x axis like before\n", "ax_x = bqplot.Axis(scale = x_sc, label = 'X Value')\n", "# and one y axis on the left\n", "ax_y1 = bqplot.Axis(scale = y_sc1, label = 'Y1 Value', \n", " orientation = 'vertical')\n", "# and one y-axis on the right\n", "ax_y2 = bqplot.Axis(scale = y_sc2, label = 'Y2 Value', \n", " orientation = 'vertical', side = 'right')" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "scrolled": false }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "59355abf58c042cb98af9c650c9602f4", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='X Value', scale=LinearScale(max=10.0, min=1.0), side='bottom'), Axis(label='Y1 Value'…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# lets allow pan and zoom\n", "pz = bqplot.interacts.PanZoom(scales = {'x': [x_sc], \n", " 'y': [y_sc1, y_sc2]})\n", "#bqplot.interacts.PanZoom?\n", "fig = bqplot.Figure(marks = [lines1, lines2], \n", " axes = [ax_x, ax_y1, ax_y2], interaction=pz)\n", "#display(fig)\n", "fig\n", "# now we note if we zoom out too far, or pan to too negative of the x-axis\n", "# we lose a line\n", "\n", "# why? because the line is log-scaled, and log(numbers < 0) is undefined\n", "\n", "# this is a way in which declaritive programming can fail because there\n", "# aren't obvious options to inhibit pan&zoom to a positive range" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### END ASIDE/SKIP" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "FYI lots of more fun notebooks here: https://github.com/dmadeka/PyGotham-2017\n", "\n", "From video tutorial here: https://www.youtube.com/watch?v=rraXF0EjRC8" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Random Scatter plot\n", "Ok, lets do another quick interactive example using a scatter plot:" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [], "source": [ "# 1. Data\n", "x = np.random.random(100) # random points betweeon 0-1\n", "y = np.random.random(100) # random points betweeon 0-1" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([0.3435055 , 0.76371727, 0.40744332, 0.96982709, 0.98634342,\n", " 0.12496998, 0.24231708, 0.12873311, 0.27967172, 0.82592252,\n", " 0.09501297, 0.38137037, 0.9515915 , 0.62138341, 0.26915436,\n", " 0.14562592, 0.55419754, 0.90623859, 0.89815364, 0.51751726,\n", " 0.07419093, 0.94814202, 0.90442011, 0.4000033 , 0.00578212,\n", " 0.5060723 , 0.94328686, 0.70359194, 0.94831105, 0.77787102,\n", " 0.01269121, 0.3451176 , 0.6057318 , 0.1559008 , 0.84754417,\n", " 0.73636787, 0.25698605, 0.77345274, 0.53704964, 0.11640579,\n", " 0.21074182, 0.54813691, 0.20863306, 0.38535064, 0.60341655,\n", " 0.06714227, 0.75206259, 0.58802088, 0.49392403, 0.40245213,\n", " 0.10339404, 0.48684799, 0.9276683 , 0.63327124, 0.32511625,\n", " 0.70907261, 0.7890314 , 0.3313958 , 0.01291257, 0.50633976,\n", " 0.30047141, 0.13946659, 0.19834728, 0.60078139, 0.89597683,\n", " 0.13849559, 0.37042304, 0.61286123, 0.08120269, 0.74202002,\n", " 0.412445 , 0.62006059, 0.0880647 , 0.97956794, 0.89972789,\n", " 0.94551597, 0.9555162 , 0.1302211 , 0.53714742, 0.40698455,\n", " 0.50214983, 0.21101398, 0.93408091, 0.75971516, 0.41382418,\n", " 0.5536015 , 0.30284548, 0.7835685 , 0.91094126, 0.25294276,\n", " 0.64857772, 0.90787148, 0.37357868, 0.02570787, 0.56185796,\n", " 0.56237718, 0.48178765, 0.25949369, 0.03325126, 0.89440572]),\n", " array([0.55339193, 0.00411245, 0.99025222, 0.20848649, 0.40344564,\n", " 0.76019541, 0.34593607, 0.21663173, 0.2542929 , 0.80978514,\n", " 0.93404663, 0.94231489, 0.99490498, 0.23407717, 0.06673304,\n", " 0.66963738, 0.72261352, 0.98516188, 0.06349932, 0.72545111,\n", " 0.174635 , 0.60803744, 0.79983438, 0.64760632, 0.63794048,\n", " 0.19312944, 0.76156306, 0.60511633, 0.58378065, 0.56592431,\n", " 0.74901001, 0.36246637, 0.5419876 , 0.72088437, 0.38345922,\n", " 0.24086524, 0.6750163 , 0.17430649, 0.93918672, 0.16676553,\n", " 0.87975429, 0.57997601, 0.48654043, 0.71310511, 0.15412865,\n", " 0.64478368, 0.40363455, 0.94236593, 0.10093919, 0.046239 ,\n", " 0.60291287, 0.00274582, 0.80238584, 0.05692085, 0.96400247,\n", " 0.93366903, 0.48561359, 0.14250506, 0.46255288, 0.16116064,\n", " 0.59488858, 0.80657319, 0.79992302, 0.5767332 , 0.73356725,\n", " 0.44489974, 0.10411241, 0.73461317, 0.86224465, 0.00146645,\n", " 0.24460884, 0.58604608, 0.88519797, 0.40881751, 0.5985214 ,\n", " 0.5727166 , 0.53067539, 0.65251656, 0.10847169, 0.4926742 ,\n", " 0.8454396 , 0.01245871, 0.34358987, 0.10984166, 0.32859091,\n", " 0.19282801, 0.73560367, 0.28536102, 0.16795675, 0.78323625,\n", " 0.07794957, 0.87587978, 0.98742467, 0.55499922, 0.48752317,\n", " 0.49048991, 0.97289468, 0.18239701, 0.76754169, 0.93726769]))" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x, y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create scales and axis like we did before:" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "# 2. scales\n", "x_sc = bqplot.LinearScale()\n", "y_sc = bqplot.LinearScale()\n", "\n", "# 3. axis\n", "x_ax = bqplot.Axis(scale = x_sc, label = 'X')\n", "y_ax = bqplot.Axis(scale = y_sc, label = 'Y', orientation = 'vertical')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create a scatter plot graphing object with these random x/y and scales:" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mScatter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "Scatter mark.\n", "\n", "In the case of the Scatter mark, scales for 'x' and 'y' MUST be provided.\n", "The scales of other data attributes are optional. In the case where another\n", "data attribute than 'x' or 'y' is provided but the corresponding scale is\n", "missing, the data attribute is ignored.\n", "\n", "Attributes\n", "----------\n", "icon: string (class-level attribute)\n", " Font-awesome icon for that mark\n", "name: string (class-level attribute)\n", " User-friendly name of the mark\n", "marker: {'circle', 'cross', 'diamond', 'square', 'triangle-down', 'triangle-up', 'arrow', 'rectangle', 'ellipse'}\n", " Marker shape\n", "colors: list of colors (default: ['steelblue'])\n", " List of colors of the markers. If the list is shorter than the number\n", " of points, the colors are reused.\n", "default_colors: Deprecated\n", " Same as `colors`, deprecated as of version 0.8.4\n", "fill: Bool (default: True)\n", " Whether to fill the markers or not\n", "stroke: Color or None (default: None)\n", " Stroke color of the marker\n", "stroke_width: Float (default: 1.5)\n", " Stroke width of the marker\n", "opacities: list of floats (default: [1.0])\n", " Default opacities of the markers. If the list is shorter than\n", " the number\n", " of points, the opacities are reused.\n", "default_skew: float (default: 0.5)\n", " Default skew of the marker.\n", " This number is validated to be between 0 and 1.\n", "default_size: nonnegative int (default: 64)\n", " Default marker size in pixel.\n", " If size data is provided with a scale, default_size stands for the\n", " maximal marker size (i.e. the maximum value for the 'size' scale range)\n", "drag_size: nonnegative float (default: 5.)\n", " Ratio of the size of the dragged scatter size to the default\n", " scatter size.\n", "names: numpy.ndarray (default: None)\n", " Labels for the points of the chart\n", "display_names: bool (default: True)\n", " Controls whether names are displayed for points in the scatter\n", "label_display_horizontal_offset: float (default: None)\n", " Adds an offset, in pixels, to the horizontal positioning of the 'names'\n", " label above each data point\n", "label_display_vertical_offset: float (default: None)\n", " Adds an offset, in pixels, to the vertical positioning of the 'names'\n", " label above each data point\n", "enable_move: bool (default: False)\n", " Controls whether points can be moved by dragging. Refer to restrict_x,\n", " restrict_y for more options.\n", "restrict_x: bool (default: False)\n", " Restricts movement of the point to only along the x axis. This is valid\n", " only when enable_move is set to True. If both restrict_x and restrict_y\n", " are set to True, the point cannot be moved.\n", "restrict_y: bool (default: False)\n", " Restricts movement of the point to only along the y axis. This is valid\n", " only when enable_move is set to True. If both restrict_x and restrict_y\n", " are set to True, the point cannot be moved.\n", "\n", "\n", "Data Attributes\n", "\n", "x: numpy.ndarray (default: [])\n", " abscissas of the data points (1d array)\n", "y: numpy.ndarray (default: [])\n", " ordinates of the data points (1d array)\n", "color: numpy.ndarray or None (default: None)\n", " color of the data points (1d array). Defaults to default_color when not\n", " provided or when a value is NaN\n", "opacity: numpy.ndarray or None (default: None)\n", " opacity of the data points (1d array). Defaults to default_opacity when\n", " not provided or when a value is NaN\n", "size: numpy.ndarray or None (default: None)\n", " size of the data points. Defaults to default_size when not provided or\n", " when a value is NaN\n", "skew: numpy.ndarray or None (default: None)\n", " skewness of the markers representing the data points. Defaults to\n", " default_skew when not provided or when a value is NaN\n", "rotation: numpy.ndarray or None (default: None)\n", " orientation of the markers representing the data points.\n", " The rotation scale's range is [0, 180]\n", " Defaults to 0 when not provided or when a value is NaN.\n", "\n", "Notes\n", "-----\n", "The fields which can be passed to the default tooltip are:\n", " All the data attributes\n", " index: index of the marker being hovered on\n", "The following are the events which can trigger interactions:\n", " click: left click of the mouse\n", " hover: mouse-over an element\n", "The following are the interactions which can be linked to the above events:\n", " tooltip: display tooltip\n", " add: add new points to the scatter (can only linked to click)\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/marks.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m ScatterGL" ] } ], "source": [ "bqplot.Scatter?" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "# 4. marks\n", "scatters = bqplot.Scatter(x = x,\n", " y = y,\n", " scales = {'x': x_sc, 'y': y_sc})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, lets create a selector to select points along the x-axis. We will use the `bqplot` interaction called `FastIntervalSelector`:" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minteracts\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFastIntervalSelector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "Fast interval selector interaction.\n", "\n", "This 1-D selector is used to select an interval on the x-scale\n", "by just moving the mouse (without clicking or dragging). The\n", "x-coordinate of the mouse controls the mid point of the interval selected\n", "while the y-coordinate of the mouse controls the the width of the interval.\n", "The larger the y-coordinate, the wider the interval selected.\n", "\n", "Interval selector has three modes:\n", " 1. default mode: This is the default mode in which the mouse controls\n", " the location and width of the interval.\n", " 2. fixed-width mode: In this mode the width of the interval is frozen\n", " and only the location of the interval is controlled with the\n", " mouse.\n", " A single click from the default mode takes you to this mode.\n", " Another single click takes you back to the default mode.\n", " 3. frozen mode: In this mode the selected interval is frozen and the\n", " selector does not respond to mouse move.\n", " A double click from the default mode takes you to this mode.\n", " Another double click takes you back to the default mode.\n", "\n", "Attributes\n", "----------\n", "selected: numpy.ndarray\n", " Two-element array containing the start and end of the interval selected\n", " in terms of the scale of the selector.\n", "color: Color or None (default: None)\n", " color of the rectangle representing the interval selector\n", "size: Float or None (default: None)\n", " if not None, this is the fixed pixel-width of the interval selector\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/interacts.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.interacts.FastIntervalSelector?" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [], "source": [ "# 5. interactions\n", "selector = bqplot.interacts.FastIntervalSelector(\n", " scale = x_sc, marks = [scatters]) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's check out the full figure + interaction!" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "89429349a0bf4e27a7b516ed54358efb", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='X', scale=LinearScale(), side='bottom'), Axis(label='Y', orientation='vertical', scal…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Finally: fig\n", "fig = bqplot.Figure(marks = [scatters], axes = [x_ax, y_ax], interaction = selector)\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This might depend on what computer you are in, but on my mac, I click to start selecting and then double click to \"lock in\" my selected region.\n", "\n", "How do we tell what interval we have selected?" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.50258896, 0.77832084])" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selector.selected" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, its a little hard to see what points are selected. There are some hidden tags within our scatter plot points that we can mess with to change our our plot looks. These have CSS styling (how HTML is styled), so they'll look very un-Pythonic, because they are! We'll get more into this sort of thing when we are doing Javascript later in the course:" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [], "source": [ "scatters.unselected_style={'opacity': 0.8} # when unselected, make the points a little see-through\n", "\n", "scatters.selected_style={'fill': 'red', 'stroke': 'yellow'} # fill with red, outline in yellow when selected" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Display updated figure (though, like with widgets, this change is \"backreactive\" and will show up above as well):" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "16be3e37d93b4ed3b376901f8e421f81", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='X', scale=LinearScale(), side='bottom'), Axis(label='Y', orientation='vertical', scal…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = bqplot.Figure(marks = [scatters], axes = [x_ax, y_ax], interaction = selector)\n", "#display(fig)\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Random Heatmap with 2D data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets start thinking about heatmaps with some random data:" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.14284753, 0.3380296 , 0.73463165, 0.63509897, 0.71019444,\n", " 0.74894827, 0.74914147, 0.07587777, 0.75921986, 0.65655436],\n", " [0.29616338, 0.68277265, 0.21517136, 0.09876002, 0.48454085,\n", " 0.34799351, 0.86251608, 0.1368085 , 0.99257749, 0.79515285],\n", " [0.81974292, 0.7146941 , 0.31752821, 0.82403985, 0.68091556,\n", " 0.23376115, 0.90606687, 0.13628151, 0.61290948, 0.90511471],\n", " [0.88063726, 0.95907589, 0.09384309, 0.95964782, 0.25278208,\n", " 0.53517774, 0.62948673, 0.77508677, 0.80860543, 0.65787508],\n", " [0.74088926, 0.63896199, 0.14373814, 0.71195645, 0.23010547,\n", " 0.36456378, 0.1781329 , 0.12067858, 0.33969262, 0.84033688],\n", " [0.04083225, 0.24513688, 0.61656894, 0.02948847, 0.34685374,\n", " 0.84207837, 0.87978381, 0.36584584, 0.29818493, 0.47188003],\n", " [0.53804911, 0.82707316, 0.73901657, 0.39588482, 0.10054385,\n", " 0.95397658, 0.85036869, 0.85717319, 0.36422885, 0.10371468],\n", " [0.18118775, 0.57211779, 0.88227489, 0.93357733, 0.70799852,\n", " 0.66462165, 0.24593237, 0.86140739, 0.87335435, 0.22181375],\n", " [0.31658879, 0.81117524, 0.5152646 , 0.80823036, 0.38427456,\n", " 0.62342032, 0.29618928, 0.91169646, 0.25069897, 0.98448014],\n", " [0.69817092, 0.09351698, 0.77240092, 0.38773586, 0.42666405,\n", " 0.53492401, 0.70235503, 0.09603961, 0.53687729, 0.84879035]])" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 1. Data\n", "data = np.random.random((10, 10))\n", "data # your's may look different!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we just have a 10 x 10 array here.\n", "\n", "Lets start by generating a quick heat map with `bqplot`'s `GridHeatMap` marks function. We've been making plots with linear scales before, but now for a heatmap, we will want to make a color scale as well. Let's start with just assigning a color scale, and going from there:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mColorScale\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "A color scale.\n", "\n", "A mapping from numbers to colors. The relation is affine by part.\n", "\n", "Attributes\n", "----------\n", "scale_type: {'linear'}\n", " scale type\n", "colors: list of colors (default: [])\n", " list of colors\n", "min: float or None (default: None)\n", " if not None, min is the minimal value of the domain\n", "max: float or None (default: None)\n", " if not None, max is the maximal value of the domain\n", "mid: float or None (default: None)\n", " if not None, mid is the value corresponding to the mid color.\n", "scheme: string (default: 'RdYlGn')\n", " Colorbrewer color scheme of the color scale.\n", "extrapolation: {'constant', 'linear'} (default: 'constant')\n", " How to extrapolate values outside the [min, max] domain.\n", "rtype: string (class-level attribute)\n", " The range type of a color scale is 'Color'. This should not be modified.\n", "dtype: type (class-level attribute)\n", " the associated data type / domain type\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/scales.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m DateColorScale, OrdinalColorScale" ] } ], "source": [ "bqplot.ColorScale?" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "86afa1a19d5f4fae9ab584073a5d7cb9", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(fig_margin={'top': 60, 'bottom': 60, 'left': 60, 'right': 60}, marks=[GridHeatMap(color=array([[0.14284…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# 2. Scales -- in this case, just a color scale\n", "col_sc = bqplot.ColorScale() # use bqplot to define a color scale\n", "\n", "# 3. Axis -- skipping for now\n", "\n", "# 4. Mark -- use colorscale to make make heatmap of our data:\n", "heat_map = bqplot.GridHeatMap(color = data, scales = {'color': col_sc})\n", "\n", "# 5. Skipping any interactions\n", "\n", "# Finally: figure\n", "fig = bqplot.Figure(marks = [heat_map])\n", "fig" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGridHeatMap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "GridHeatMap mark.\n", "\n", "Alignment: The tiles can be aligned so that the data matches either the\n", "start, the end or the midpoints of the tiles. This is controlled by the\n", "align attribute.\n", "\n", "Suppose the data passed is a m-by-n matrix. If the scale for the rows is\n", "Ordinal, then alignment is by default the mid points. For a non-ordinal\n", "scale, the data cannot be aligned to the mid points of the rectangles.\n", "\n", "If it is not ordinal, then two cases arise. If the number of rows passed\n", "is m, then align attribute can be used. If the number of rows passed\n", "is m+1, then the data are the boundaries of the m rectangles.\n", "\n", "If rows and columns are not passed, and scales for them are also\n", "not passed, then ordinal scales are generated for the rows and columns.\n", "\n", "Attributes\n", "----------\n", "row_align: Enum(['start', 'end'])\n", " This is only valid if the number of entries in `row` exactly match the\n", " number of rows in `color` and the `row_scale` is not `OrdinalScale`.\n", " `start` aligns the row values passed to be aligned with the start\n", " of the tiles and `end` aligns the row values to the end of the tiles.\n", "column_align: Enum(['start', end'])\n", " This is only valid if the number of entries in `column` exactly\n", " match the number of columns in `color` and the `column_scale` is\n", " not `OrdinalScale`. `start` aligns the column values passed to\n", " be aligned with the start of the tiles and `end` aligns the\n", " column values to the end of the tiles.\n", "anchor_style: dict (default: {})\n", " Controls the style for the element which serves as the anchor during\n", " selection.\n", "display_format: string (default: None)\n", " format for displaying values. If None, then values are not displayed\n", "font_style: dict\n", " CSS style for the text of each cell\n", "\n", "Data Attributes\n", "\n", "color: numpy.ndarray or None (default: None)\n", " color of the data points (2d array). The number of elements in\n", " this array correspond to the number of cells created in the heatmap.\n", "row: numpy.ndarray or None (default: None)\n", " labels for the rows of the `color` array passed. The length of\n", " this can be no more than 1 away from the number of rows in `color`.\n", " This is a scaled attribute and can be used to affect the height of the\n", " cells as the entries of `row` can indicate the start or the end points\n", " of the cells. Refer to the property `row_align`.\n", " If this property is None, then a uniformly spaced grid is generated in\n", " the row direction.\n", "column: numpy.ndarray or None (default: None)\n", " labels for the columns of the `color` array passed. The length of\n", " this can be no more than 1 away from the number of columns in `color`\n", " This is a scaled attribute and can be used to affect the width of the\n", " cells as the entries of `column` can indicate the start or the\n", " end points of the cells. Refer to the property `column_align`.\n", " If this property is None, then a uniformly spaced grid is generated in\n", " the column direction.\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/marks.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.GridHeatMap?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are some things we probably want to do here. One of them being able to change the color scale -- we spent all this time thinking about color, let's put those thoughts to good use!" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "23b41dccaa08473b96ac5eb8391e87ea", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(fig_margin={'top': 60, 'bottom': 60, 'left': 60, 'right': 60}, marks=[GridHeatMap(color=array([[0.14284…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# 1. Data -- same as above\n", "\n", "# 2. Scale -- color with new color scheme\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\") # color scheme of reds\n", "\n", "# 3. skip Axis\n", "\n", "# 4. Marks -- use colorscale to make make heatmap of our data:\n", "heat_map = bqplot.GridHeatMap(color = data, scales = {'color': col_sc})\n", "\n", "# Figure\n", "fig = bqplot.Figure(marks = [heat_map])\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's build upon this plot by adding some axis so that we can think about how to label our data. Currently, we *only* have a color scale for each color in our 10x10 grid, so we will label this color axis with a colorbar:" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mbqplot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mColorAxis\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m't.Any'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m \n", "A colorbar axis.\n", "\n", "A color axis is the visual representation of a color scale.\n", "\n", "Attributes\n", "----------\n", "scale: ColorScale\n", " The scale represented by the axis\n", "\u001b[0;31mInit docstring:\u001b[0m Public constructor\n", "\u001b[0;31mFile:\u001b[0m /opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/bqplot/axes.py\n", "\u001b[0;31mType:\u001b[0m MetaHasTraits\n", "\u001b[0;31mSubclasses:\u001b[0m " ] } ], "source": [ "bqplot.ColorAxis?" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "221612257ae440d1add88be1a6b02d3e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds'), side='right')], fig_margin={'t…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# 1. Data -- same\n", "\n", "# 2. Scales -- color\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "# lets plot some axes on our plot as well, in this case\n", "# our axis will be a color bar, vertically on the right\n", "# of our heatmap\n", "\n", "# 3. Axis (finally) -- here a \"color axis\" is just a colorbar\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right') \n", "\n", "# 4. Marks -- put it all together and lets take a look!\n", "heat_map = bqplot.GridHeatMap(color = data, \n", " scales = {'color': col_sc})\n", "\n", "# 5. no interactions\n", "\n", "# generate fig!\n", "fig = bqplot.Figure(marks = [heat_map], axes = [c_ax])\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While our x/y bins don't mean anything in particular in this case - we are dealing with just a randomly binned 10x10 dataset after all - we none-the-less probably want to put at least the bin labels on there. So let's make some x/y scales and some x/y axis (note I'm doing Scales/Axis a bit out of order here to group by kind of axis):" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [], "source": [ "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why did I choose ordinal scales? Well again - the bins, while numbered, don't actually mean anything, so in a sense they are \"categorical\" bins, and our scales should reflect that fact!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Color scale & color axis like before:" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [], "source": [ "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Combine all these scales, axis and the data into a heat map mark using `bqplot.GridHeatMap`:" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [], "source": [ "# Marks -- lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = data,\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... put these marks and axes on a figure canvas and plot it!" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d47f31450ef24c01958cf84ad2e91777", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds'), side='right'), Axis(orientatio…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# stir and combine into 1 figure\n", "fig = bqplot.Figure(marks = [heat_map], \n", " axes = [c_ax, y_ax, x_ax])\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, we are almost there -- we don't have any interactivity! Let's get into it. In this case, we can actually specify the interaction when we are constructing the `GridHeatMap` mark. Let's do something when we click on each square. We can do this with a `click-select` interaction. " ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4a715465a0544552bc4091e4472ad001", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds'), side='right'), Axis(orientatio…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# 1. Data -- same\n", "\n", "# 2. Scales -- recopy our scales and axis for posterity:\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# 3. Axis -- create axis - for colors, x & y\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')\n", "\n", "# 4. Marks -- lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = data,\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not\n", "\n", "# 5. Note: I put interactions in my marks call here!\n", "\n", "# stir and combine into 1 figure\n", "fig = bqplot.Figure(marks = [heat_map], \n", " axes = [c_ax, y_ax, x_ax])\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok fine, but our selection isn't linked to anything! Lets check out what heat_map selected actually is before we decide to do something with it:" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[4, 3]])" ] }, "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ "heat_map.selected\n", "# note if I select a different box & re-run this cell,\n", "# I get out different values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So the *trait* of the heat_map that gets updated when we select a box is the x/y indicies.\n", "\n", "Let's start simple: write a little function that links the data value to the selected & lets print this in a little ipywidgets label:" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [], "source": [ "mySelectedLabel = ipywidgets.Label()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's now define what happens to this label when our heatmap is selected. If we recall back to when we started learning about ipywidgets and traitlets, this will be a change in the `mySelectedLabel` widget's *value* when a *trait* of our `GridHeatMap` object changes.\n", "\n", "First, let's define this action, starting simple: just print out whatever is selected:" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [], "source": [ "def on_selected(change):\n", " print(change)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's re-construct our heatmap and link this `on_selected` function to the *trait* of the selected heatmap:" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d424a5cbc4184232a890c3f1ce38e469", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds'), side='right'), Axis(orientatio…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "{'name': 'selected', 'old': None, 'new': array([[4, 4]]), 'owner': GridHeatMap(anchor_style={'fill': 'blue'}, color=array([[0.14284753, 0.3380296 , 0.73463165, 0.63509897, 0.71019444,\n", " 0.74894827, 0.74914147, 0.07587777, 0.75921986, 0.65655436],\n", " [0.29616338, 0.68277265, 0.21517136, 0.09876002, 0.48454085,\n", " 0.34799351, 0.86251608, 0.1368085 , 0.99257749, 0.79515285],\n", " [0.81974292, 0.7146941 , 0.31752821, 0.82403985, 0.68091556,\n", " 0.23376115, 0.90606687, 0.13628151, 0.61290948, 0.90511471],\n", " [0.88063726, 0.95907589, 0.09384309, 0.95964782, 0.25278208,\n", " 0.53517774, 0.62948673, 0.77508677, 0.80860543, 0.65787508],\n", " [0.74088926, 0.63896199, 0.14373814, 0.71195645, 0.23010547,\n", " 0.36456378, 0.1781329 , 0.12067858, 0.33969262, 0.84033688],\n", " [0.04083225, 0.24513688, 0.61656894, 0.02948847, 0.34685374,\n", " 0.84207837, 0.87978381, 0.36584584, 0.29818493, 0.47188003],\n", " [0.53804911, 0.82707316, 0.73901657, 0.39588482, 0.10054385,\n", " 0.95397658, 0.85036869, 0.85717319, 0.36422885, 0.10371468],\n", " [0.18118775, 0.57211779, 0.88227489, 0.93357733, 0.70799852,\n", " 0.66462165, 0.24593237, 0.86140739, 0.87335435, 0.22181375],\n", " [0.31658879, 0.81117524, 0.5152646 , 0.80823036, 0.38427456,\n", " 0.62342032, 0.29618928, 0.91169646, 0.25069897, 0.98448014],\n", " [0.69817092, 0.09351698, 0.77240092, 0.38773586, 0.42666405,\n", " 0.53492401, 0.70235503, 0.09603961, 0.53687729, 0.84879035]]), interactions={'click': 'select'}, scales={'color': ColorScale(scheme='Reds'), 'row': OrdinalScale(), 'column': OrdinalScale()}, scales_metadata={'row': {'orientation': 'vertical', 'dimension': 'y'}, 'column': {'orientation': 'horizontal', 'dimension': 'x'}, 'color': {'dimension': 'color'}}, selected=array([[4, 4]]), selected_style={'opacity': 1.0}, tooltip_style={'opacity': 0.9}, unselected_style={'opacity': 0.8}), 'type': 'change'}\n" ] } ], "source": [ "# 1. Data -- same\n", "\n", "# 2. Scales -- recopy our scales and axis for posterity:\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# 3. Axis -- create axis - for colors, x & y\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')\n", "\n", "# 4. Marks -- lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = data,\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not\n", "\n", "# 5. Note: I put interactions in my marks call here. BUT now we want to actually DO something with our selection\n", "## THIS IS ALL WE HAVE ADDED!\n", "heat_map.observe(on_selected, 'selected')\n", "\n", "\n", "# stir and combine into 1 figure\n", "fig = bqplot.Figure(marks = [heat_map], \n", " axes = [c_ax, y_ax, x_ax])\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, we can see that what gets printed out when we select is a dictionary -- this is like a \"change\" dictionary with an ipywidget that we played with last week and it has some familiar things in it like the `old` and `new` keys, the `owner` key, etc. \n", "\n", "Check out the `selected=array...` in the above -- this is how we can grab the index of whatever grid we have selected.\n", "\n", "It also is storing a lot of information about our `GridHeatMap` plot! \n", "\n", "Since the change \"owner\" is our heat_map marks, let's print these out so we can access the indicies. Once we do this, we can then use these indicies to access the data in our plot. \n", "\n", "One thing at a time though - let's just print out the selected indicies:" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [], "source": [ "def on_selected(change):\n", " print(change['owner'].selected)" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b9bd7c7453bc45aca81d17f1b57ce0e4", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds'), side='right'), Axis(orientatio…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "[[1 2]]\n" ] } ], "source": [ "# 1. Data -- same\n", "\n", "# 2. Scales -- recopy our scales and axis for posterity:\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# 3. Axis -- create axis - for colors, x & y\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')\n", "\n", "# 4. Marks -- lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = data,\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not\n", "\n", "# 5. Note: I put interactions in my marks call here. BUT now we want to actually DO something with our selection\n", "## THIS IS ALL WE HAVE ADDED!\n", "heat_map.observe(on_selected, 'selected')\n", "\n", "\n", "# stir and combine into 1 figure\n", "fig = bqplot.Figure(marks = [heat_map], \n", " axes = [c_ax, y_ax, x_ax])\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It turns out we can actually `SHIFT-select` and select a selection of boxes at one time. Depending on your browser/OS you can also use `CMD` or `CTRL`-select to select individual boxes. However, this is a little bit dependent on how your browser/trackpad/mouse is set up.\n", "\n", "(`CMD`-select doesn't work in notebooks on my Mac, but it does in jupyter-lab. Go fig.)\n", "\n", "**DEMONSTRATE**\n", "\n", "Because of this we actual `selected` set of indicies we want to access can be more than just x/y indicies - it can be an array of indicies representing all of the `SHIFT-select`ed points.\n", "\n", "For the sake of simplicity, let's *only* do things when we select one point. So we will *only* take out the first element of this selected array. Let's print this with our `on_selected` function:" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [], "source": [ "def on_selected(change):\n", " if len(change['owner'].selected) == 1:\n", " print(change['owner'].selected[0])\n", " # else: don't update anything" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "572837e713ab4c82b7b4ec5de1d578a9", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds'), side='right'), Axis(orientatio…" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "[3 3]\n" ] } ], "source": [ "# 1. Data -- same\n", "\n", "# 2. Scales -- recopy our scales and axis for posterity:\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# 3. Axis -- create axis - for colors, x & y\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')\n", "\n", "# 4. Marks -- lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = data,\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not\n", "\n", "# 5. Note: I put interactions in my marks call here. BUT now we want to actually DO something with our selection\n", "## THIS IS ALL WE HAVE ADDED!\n", "heat_map.observe(on_selected, 'selected')\n", "\n", "\n", "# stir and combine into 1 figure\n", "fig = bqplot.Figure(marks = [heat_map], \n", " axes = [c_ax, y_ax, x_ax])\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you'll notice if I `SHIFT-select` I still only get the set of x/y indicies associated with the blue selected point. Sweet.\n", "\n", "Now that we have these indicies in our plot, we can (finally) update our Label widget to print out what the data value is at this point:" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [], "source": [ "def on_selected(change):\n", " if len(change['owner'].selected) == 1: #only 1 selected\n", " i, j = change['owner'].selected[0] # grab the x/y coordinates\n", " v = data[i,j] # grab data value\n", " mySelectedLabel.value = 'Data Value = ' + str(v) # set our label" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we just have to make sure we show both of the figure and the label when we display:" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "49e7d22cacc642fa9c0f0fbb9e8c6f43", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Label(value=''), Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds')…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# 1. Data -- same\n", "\n", "# 2. Scales -- recopy our scales and axis for posterity:\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# 3. Axis -- create axis - for colors, x & y\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')\n", "\n", "# 4. Marks -- lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = data,\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not\n", "\n", "# 5. Note: I put interactions in my marks call here. BUT now we want to actually DO something with our selection\n", "## THIS IS ALL WE HAVE ADDED!\n", "heat_map.observe(on_selected, 'selected')\n", "\n", "\n", "# stir and combine into 1 figure\n", "fig = bqplot.Figure(marks = [heat_map], \n", " axes = [c_ax, y_ax, x_ax])\n", "\n", "\n", "# ADDED: putting widget and plot together\n", "myDashboard = ipywidgets.VBox([mySelectedLabel, fig]) # have label on top of fig\n", "myDashboard # show" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hey neat! So now when we do an action, in this case selecting, on one object (the heat map) we have updates *tied to* another object, in this case we are also updating our label widget's value.\n", "\n", "This is how we will build up dashboards that allow us to display different aspects of our data in linked views.\n", "\n", "Let's practice one of these linked views now by making a linked histogram of a 3D dataset instead of printing the value with a 2D dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Random heatmap + Histogram with 3D data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's move on to making a preliminary dashboard for multi-dimensional datasets. Let's first start with some randomly generated data again, this time in 3D:" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "array([[[0.20839745, 0.05656519, 0.05452723, ..., 0.20246552,\n", " 0.03653982, 0.02856585],\n", " [0.3075706 , 0.97911968, 0.27745423, ..., 0.68661977,\n", " 0.12838299, 0.37250473],\n", " [0.37905927, 0.95978522, 0.14497447, ..., 0.22513639,\n", " 0.27685587, 0.92725913],\n", " ...,\n", " [0.78381126, 0.01574558, 0.10247296, ..., 0.27899025,\n", " 0.36285904, 0.65738112],\n", " [0.97946477, 0.64369808, 0.2812343 , ..., 0.75975654,\n", " 0.5496779 , 0.30941392],\n", " [0.16422874, 0.04022129, 0.82579274, ..., 0.17743916,\n", " 0.73526023, 0.93455809]],\n", "\n", " [[0.21245077, 0.13852174, 0.24709633, ..., 0.99957549,\n", " 0.68135117, 0.13456627],\n", " [0.19027549, 0.74391005, 0.78789125, ..., 0.97961767,\n", " 0.41895727, 0.92666144],\n", " [0.84718441, 0.93180425, 0.87482394, ..., 0.57923216,\n", " 0.93040524, 0.24981458],\n", " ...,\n", " [0.06925724, 0.38768369, 0.41581523, ..., 0.65380612,\n", " 0.39250303, 0.37995476],\n", " [0.22257734, 0.9708368 , 0.6468318 , ..., 0.88039936,\n", " 0.57524528, 0.26335216],\n", " [0.16191646, 0.1886926 , 0.40613677, ..., 0.16788061,\n", " 0.87594714, 0.44580059]],\n", "\n", " [[0.66498529, 0.99100554, 0.70416341, ..., 0.49417669,\n", " 0.97223038, 0.62251468],\n", " [0.00517497, 0.12005638, 0.89586399, ..., 0.66793058,\n", " 0.98626408, 0.59587566],\n", " [0.12205437, 0.55071132, 0.2836498 , ..., 0.42894583,\n", " 0.69398602, 0.26736468],\n", " ...,\n", " [0.34238073, 0.63658522, 0.83925968, ..., 0.84986392,\n", " 0.00112633, 0.43407567],\n", " [0.82137142, 0.54020255, 0.664424 , ..., 0.98131282,\n", " 0.4161766 , 0.7739654 ],\n", " [0.92632068, 0.69744763, 0.19913346, ..., 0.01195727,\n", " 0.34577941, 0.59435783]],\n", "\n", " ...,\n", "\n", " [[0.98489305, 0.32768683, 0.67553511, ..., 0.60721211,\n", " 0.26748695, 0.58850961],\n", " [0.82933614, 0.33840828, 0.91770601, ..., 0.74800729,\n", " 0.0778428 , 0.40669981],\n", " [0.66130039, 0.81000402, 0.58393881, ..., 0.66036405,\n", " 0.66632665, 0.52623413],\n", " ...,\n", " [0.41014622, 0.97012091, 0.21140687, ..., 0.01651799,\n", " 0.54097759, 0.58009787],\n", " [0.32296838, 0.23469038, 0.76153328, ..., 0.03536765,\n", " 0.97845771, 0.06541837],\n", " [0.457106 , 0.81514845, 0.74798809, ..., 0.44305803,\n", " 0.9352139 , 0.44814522]],\n", "\n", " [[0.40211808, 0.80918265, 0.40786659, ..., 0.67817562,\n", " 0.33582677, 0.13045838],\n", " [0.91065575, 0.64173865, 0.26202183, ..., 0.38017801,\n", " 0.40653675, 0.57640588],\n", " [0.49269946, 0.22092694, 0.40410305, ..., 0.42563718,\n", " 0.86882546, 0.4881152 ],\n", " ...,\n", " [0.33640916, 0.20097263, 0.13277369, ..., 0.8979904 ,\n", " 0.21156274, 0.9746394 ],\n", " [0.94492747, 0.79806641, 0.8233174 , ..., 0.84623074,\n", " 0.99087129, 0.10888821],\n", " [0.03066272, 0.06271083, 0.10346125, ..., 0.50042705,\n", " 0.81222428, 0.30811636]],\n", "\n", " [[0.74628819, 0.92473615, 0.77649587, ..., 0.01935957,\n", " 0.72995409, 0.33752134],\n", " [0.92815592, 0.21363362, 0.74531152, ..., 0.44794056,\n", " 0.10320434, 0.8675112 ],\n", " [0.22809656, 0.52121735, 0.48091181, ..., 0.34966295,\n", " 0.47769247, 0.9133561 ],\n", " ...,\n", " [0.13014716, 0.88253152, 0.32721467, ..., 0.10574428,\n", " 0.2718303 , 0.83362905],\n", " [0.25275394, 0.52198697, 0.38025543, ..., 0.40968153,\n", " 0.34307534, 0.18524501],\n", " [0.88998214, 0.4565188 , 0.19460985, ..., 0.12256277,\n", " 0.36822491, 0.18952976]]])" ] }, "execution_count": 91, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data = np.random.random((10, 10,20))\n", "data" ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(10, 10, 20)" ] }, "execution_count": 92, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.shape" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.20839745, 0.05656519, 0.05452723, 0.32087035, 0.38180842,\n", " 0.33570676, 0.42846837, 0.43793525, 0.53246388, 0.05584211,\n", " 0.08300064, 0.03453802, 0.37140471, 0.90016435, 0.30622248,\n", " 0.94279224, 0.6446468 , 0.20246552, 0.03653982, 0.02856585])" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data[0,0,:]\n", "# we can see that no instead of 1 value, each \"i,j\" component\n", "# has an array of values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Make the heatmap plot like before.\n", "\n", "First, scales and axis (x/y and color):" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [], "source": [ "# Scales: now add scales - colors, x & y\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# Axis: create axis - for colors, x & y\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, instead of feeding in `data` like we did in 2D for our heatmap, let's feed in the sum of data along the z-axis:" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [], "source": [ "# Marks: lets now re-do our heat map & add in some interactivity:\n", "heat_map = bqplot.GridHeatMap(color = np.sum(data, axis=2),\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's include our label in our dashboard:" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [], "source": [ "# create label again\n", "mySelectedLabel = ipywidgets.Label()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This time, let's have the data print out the sum along the z-axis instead of just a data value at an x/y index position:" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [], "source": [ "def on_selected(change):\n", " if len(change['owner'].selected) == 1: #only 1 selected\n", " i, j = change['owner'].selected[0] # grab the x/y coordinates\n", " v = data[i,j].sum() # grab data value at x/y index and sum along z\n", " mySelectedLabel.value = 'Data Sum = ' + str(v) # set our label" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Observe change:" ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [], "source": [ "heat_map.observe(on_selected, 'selected')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Combine and display:" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "dc8b6fbcb673465084b7acb0f663e331", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Label(value=''), Figure(axes=[ColorAxis(orientation='vertical', scale=ColorScale(scheme='Reds')…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = bqplot.Figure(marks = [heat_map], axes = [c_ax, y_ax, x_ax])\n", "\n", "myDashboard = ipywidgets.VBox([mySelectedLabel, fig])\n", "myDashboard" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ultimately what we want to do is select an x/y grid and then \"expand\" our data along z into a histogram such that if we select a grid on our heatmap a new histogram is generated that shows the distribution of values along z. \n", "\n", "Before we link these two things together, let's make a histogram of a single \"z\" value by fixing our x/y indicies:" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [], "source": [ "i,j = 0,0 # can be any combo" ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.20839745, 0.05656519, 0.05452723, 0.32087035, 0.38180842,\n", " 0.33570676, 0.42846837, 0.43793525, 0.53246388, 0.05584211,\n", " 0.08300064, 0.03453802, 0.37140471, 0.90016435, 0.30622248,\n", " 0.94279224, 0.6446468 , 0.20246552, 0.03653982, 0.02856585])" ] }, "execution_count": 101, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data[i,j] # 20 elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make a histogram. \n", "\n", "Let's think about what our x/y scales will be: we want to show the distribution of values along z. Intuitavely, we know this will go between $\\sim$0-1 since we have randomly generated values.\n", "\n", "So, our \"x\" axis for this plot should be a linear scale so it can go from 0-1, and our \"y\" axis should be linear and will show the frequency of values in each bin.\n", "\n", "Scales:" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [], "source": [ "x_sch = bqplot.LinearScale()\n", "y_sch = bqplot.LinearScale()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make axis with these scales:" ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [], "source": [ "x_axh = bqplot.Axis(scale = x_sch, label = 'Value of 3rd axis')\n", "y_axh = bqplot.Axis(scale = y_sch, \n", " orientation = 'vertical', \n", " label='Frequency')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will use `bqplot.Hist` to make this histogram:" ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [], "source": [ "hist = bqplot.Hist(sample = data[i,j,:],\n", " normalized = False, # normalized=False means we get counts in each bin\n", " scales = {'sample': x_sch, 'count': y_sch}, # sample is data values, count is frequency\n", " bins = 5) # number of bins" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note here that we specified this plot in a different way than the `GridHeatMap` and `Scatter` -- each type of `bqplot` plot has different parameters associated with the type of plot we are using." ] }, { "cell_type": "code", "execution_count": 105, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9713a0ed671d4c83b6f7113c422dfe94", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='Value of 3rd axis', scale=LinearScale(), side='bottom'), Axis(label='Frequency', orie…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "figh = bqplot.Figure(marks = [hist], axes = [x_axh, y_axh])\n", "figh" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's pause here and think about how to link up our histogram i,j with our selections on the heatmap. First, what values of the histogram can we update? Let's check:" ] }, { "cell_type": "code", "execution_count": 106, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['_model_module',\n", " '_model_module_version',\n", " '_model_name',\n", " '_view_count',\n", " '_view_module',\n", " '_view_module_version',\n", " '_view_name',\n", " 'apply_clip',\n", " 'bins',\n", " 'colors',\n", " 'count',\n", " 'display_legend',\n", " 'enable_hover',\n", " 'interactions',\n", " 'labels',\n", " 'midpoints',\n", " 'normalized',\n", " 'opacities',\n", " 'preserve_domain',\n", " 'sample',\n", " 'scales',\n", " 'scales_metadata',\n", " 'selected',\n", " 'selected_style',\n", " 'stroke',\n", " 'tooltip',\n", " 'tooltip_location',\n", " 'tooltip_style',\n", " 'unselected_style',\n", " 'visible']" ] }, "execution_count": 106, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist.keys" ] }, { "cell_type": "code", "execution_count": 107, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.20839745, 0.05656519, 0.05452723, 0.32087035, 0.38180842,\n", " 0.33570676, 0.42846837, 0.43793525, 0.53246388, 0.05584211,\n", " 0.08300064, 0.03453802, 0.37140471, 0.90016435, 0.30622248,\n", " 0.94279224, 0.6446468 , 0.20246552, 0.03653982, 0.02856585])" ] }, "execution_count": 107, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist.sample" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hey! Here is where our data values are stored! Like with when we observe changes in our heat map and update the values of our ipywidget's value we want to also update this sample's data! \n", "\n", "Let's update our `on_selected` function to reflect this:" ] }, { "cell_type": "code", "execution_count": 108, "metadata": {}, "outputs": [], "source": [ "def on_selected(change):\n", " if len(change['owner'].selected) == 1: #only 1 selected\n", " i, j = change['owner'].selected[0] # grab the x/y coordinates\n", " v = data[i,j].sum() # grab data value at x/y index and sum along z\n", " mySelectedLabel.value = 'Data Sum = ' + str(v) # set our label\n", " # NOW ALSO: update our histogram\n", " hist.sample = data[i,j,:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We don't have to go through the exersise of rebuilding our heatmap and histogram in general, but let's just do it for the sake of completeness and not accidentally re-linking thinks we shouldn't:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#1 heatmap:" ] }, { "cell_type": "code", "execution_count": 109, "metadata": {}, "outputs": [], "source": [ "# (1) Scales: x/y, colors\n", "col_sc = bqplot.ColorScale(scheme = \"Reds\")\n", "x_sc = bqplot.OrdinalScale()\n", "y_sc = bqplot.OrdinalScale()\n", "\n", "# (2) Axis: x/y, colors\n", "c_ax = bqplot.ColorAxis(scale = col_sc, \n", " orientation = 'vertical', \n", " side = 'right')\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, \n", " orientation = 'vertical')\n", "\n", "# (3) Marks: heatmap\n", "heat_map = bqplot.GridHeatMap(color = np.sum(data, axis=2),\n", " scales = {'color': col_sc,\n", " 'row': y_sc,\n", " 'column': x_sc},\n", " interactions = {'click': 'select'}, # make interactive on click of each box\n", " anchor_style = {'fill':'blue'}, # to make our selection blue\n", " selected_style = {'opacity': 1.0}, # make 100% opaque if box is selected\n", " unselected_style = {'opacity': 0.8}) # make a little see-through if not\n", "\n", "# (4) Link selection on heatmap to other things\n", "heat_map.observe(on_selected, 'selected')\n", "\n", "# (5) Paint heatmap canvas, don't display yet:\n", "fig_heatmap = bqplot.Figure(marks = [heat_map], axes = [c_ax, y_ax, x_ax])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#2 histogram:" ] }, { "cell_type": "code", "execution_count": 110, "metadata": {}, "outputs": [], "source": [ "# (1) scales: x/y, linear\n", "x_sch = bqplot.LinearScale() # range of z-axis data\n", "y_sch = bqplot.LinearScale() # frequency of z-axis data in bins\n", "\n", "# (2) axis: x/y\n", "x_axh = bqplot.Axis(scale = x_sch, label = 'Value of 3rd axis')\n", "y_axh = bqplot.Axis(scale = y_sch, \n", " orientation = 'vertical', \n", " label='Frequency')\n", "\n", "# (3) Marks: histogram - start with just 0,0 in i/j -- can do other place holders\n", "hist = bqplot.Hist(sample = data[0,0,:],\n", " normalized = False, # normalized=False means we get counts in each bin\n", " scales = {'sample': x_sch, 'count': y_sch}, # sample is data values, count is frequency\n", " bins = 5) # number of bins\n", "\n", "# (4) NO LINKING ON HISTOGRAM SIDE\n", "\n", "# (5) Paint histogram canvas, don't display yet\n", "fig_hist = bqplot.Figure(marks = [hist], axes = [x_axh, y_axh])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create dashboard layout and display:" ] }, { "cell_type": "code", "execution_count": 111, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "befd215a331948a3b459168a269d0b45", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Label(value='Data Sum = 9.561142460291283'), HBox(children=(Figure(axes=[ColorAxis(orientation=…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# side by side figures\n", "figures = ipywidgets.HBox([fig_heatmap, fig_hist])\n", "\n", "# label on top\n", "myDashboard = ipywidgets.VBox([mySelectedLabel, figures])\n", "myDashboard" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ok close, but its all smooshed! We can play with the layout of our plots before we display. To do this we use some more CSS-like styling options, in particular, `layout`:" ] }, { "cell_type": "code", "execution_count": 112, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "56137838e1ef41beb2f1989601407a90", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Label(value='Data Sum = 9.920827112180254'), HBox(children=(Figure(axes=[ColorAxis(orientation=…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# mess with figure layout:\n", "fig_heatmap.layout.min_width = '500px' # feel free to change for your screen\n", "fig_hist.layout.min_width = '500px'\n", "\n", "# side by side figures\n", "figures = ipywidgets.HBox([fig_heatmap, fig_hist])\n", "\n", "# label on top\n", "myDashboard = ipywidgets.VBox([mySelectedLabel, figures])\n", "myDashboard" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that update was \"back-reactive\" in that it changed the figure layout above as well! Super sweet!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Further complications: linking in different directions\n", "\n", "We can also apply some other links to further enhance our dashboard. One that we've messed with before is allowing the user to select the number of bins of a histogram.\n", "\n", "There are a few ways to do this, but one \"easier\" way is to just link the histogram \"bins\" with the value of a bins-slider. \n", "\n", "If we recall: `bins` was another key that was listed in hist:" ] }, { "cell_type": "code", "execution_count": 113, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['_model_module',\n", " '_model_module_version',\n", " '_model_name',\n", " '_view_count',\n", " '_view_module',\n", " '_view_module_version',\n", " '_view_name',\n", " 'apply_clip',\n", " 'bins',\n", " 'colors',\n", " 'count',\n", " 'display_legend',\n", " 'enable_hover',\n", " 'interactions',\n", " 'labels',\n", " 'midpoints',\n", " 'normalized',\n", " 'opacities',\n", " 'preserve_domain',\n", " 'sample',\n", " 'scales',\n", " 'scales_metadata',\n", " 'selected',\n", " 'selected_style',\n", " 'stroke',\n", " 'tooltip',\n", " 'tooltip_location',\n", " 'tooltip_style',\n", " 'unselected_style',\n", " 'visible']" ] }, "execution_count": 113, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist.keys" ] }, { "cell_type": "code", "execution_count": 114, "metadata": {}, "outputs": [], "source": [ "hist.bins = 5 # this changes the bins of our histogram above in a back-reactive way -- traitlets magic!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's add a little integer slider to allow our user to select the number of bins for the histogram:" ] }, { "cell_type": "code", "execution_count": 115, "metadata": {}, "outputs": [], "source": [ "bins_slider = ipywidgets.IntSlider(value=5, min=1, max=data.shape[2]) # don't make more bins than data points!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A reminder of what this looks like:" ] }, { "cell_type": "code", "execution_count": 116, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f34d6dbd20b44283b830f6fe5f6e2f89", "version_major": 2, "version_minor": 0 }, "text/plain": [ "IntSlider(value=5, max=20, min=1)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "bins_slider" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use `link` or `jslink` to link the value of this slider to our histogram's number of bins:" ] }, { "cell_type": "code", "execution_count": 117, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Link(source=(IntSlider(value=5, max=20, min=1), 'value'), target=(Hist(bins=5, colors=['steelblue'], count=arr…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ipywidgets.jslink((bins_slider, 'value'), (hist, 'bins'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While this change is \"backreactive\", let's redo our figure layout so we can see everything a bit better:" ] }, { "cell_type": "code", "execution_count": 118, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "e4b256142857467f8b7a5bc90e7f76cf", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(HBox(children=(Label(value='Data Sum = 9.920827112180254'), IntSlider(value=5, max=20, min=1)))…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# mess with figure layout:\n", "fig_heatmap.layout.min_width = '500px' # feel free to change for your screen\n", "fig_hist.layout.min_width = '500px'\n", "\n", "# side by side figures\n", "figures = ipywidgets.HBox([fig_heatmap, fig_hist])\n", "\n", "# label on top to the left, bins slider to the right\n", "controls = ipywidgets.HBox([mySelectedLabel, bins_slider])\n", "\n", "# combined\n", "myDashboard = ipywidgets.VBox([controls, figures])\n", "myDashboard" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interactive plots with bqplot's matplotlib-like interface\n", "\n", "There is a `matplotlib`-like interface in `bqplot` that we can also use to make interactive figures." ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [], "source": [ "import bqplot.pyplot as bplt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can start with our gridded heatmap:" ] }, { "cell_type": "code", "execution_count": 120, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a4c6612482e54617bab7312769118ec1", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[ColorAxis(scale=ColorScale()), Axis(orientation='vertical', scale=OrdinalScale(reverse=True)), Ax…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# first let's set up a figure object - the call is a little different for bqplot\n", "fig = bplt.figure(padding_y=0.0)\n", "\n", "# we'll call plt's gridheatmap function\n", "heat_map = bplt.gridheatmap(data[:,:,0]) # just take bottom part of data\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can make our plots interactive in much the same way as before:" ] }, { "cell_type": "code", "execution_count": 121, "metadata": {}, "outputs": [], "source": [ "# same function as before:\n", "def on_selected_bplt(change):\n", " if len(change['owner'].selected) == 1: #only 1 selected\n", " i, j = change['owner'].selected[0] # grab the x/y coordinates\n", " v = data[i,j].sum() # grab data value at x/y index and sum along z\n", " mySelectedLabel.value = 'Data Sum = ' + str(v) # set our label\n", " # NOW ALSO: update our histogram\n", " hist.sample = data[i,j,:]" ] }, { "cell_type": "code", "execution_count": 122, "metadata": {}, "outputs": [], "source": [ "mySelectedLabel = ipywidgets.Label()" ] }, { "cell_type": "code", "execution_count": 123, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b32e9e51cd2841b8901c038c5e0d3f4b", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Label(value=''), Figure(axes=[ColorAxis(scale=ColorScale(scheme='Reds'), side='top'), Axis(labe…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = bplt.figure(padding_y=0.0) # set up a figure object\n", "bplt.scales(scales={'color':bqplot.ColorScale(scheme='Reds')})\n", "\n", "# use bqplot's plt interface to plot:\n", "heat_map = bplt.gridheatmap(np.sum(data, axis=2),\n", " interactions={'click':'select'}, \n", " anchor_style = {'fill':'blue'})\n", "\n", "# hook heat_maps selected value to the label \n", "heat_map.observe(on_selected_bplt, 'selected')\n", "\n", "# change labels\n", "fig.axes[2].label = 'X' # xaxes label\n", "fig.axes[1].label = 'Y' # yaxes label\n", "\n", "# 0 is the colorbar axis\n", "fig.axes[0].orientation = 'horizontal'\n", "fig.axes[0].side = 'top' # vertical and side=right don't work rightnow?\n", "\n", "# show both the fig and label in a vertical box\n", "ipywidgets.VBox([mySelectedLabel,fig])" ] }, { "cell_type": "code", "execution_count": 124, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2b39ec4fb53f4f1087c7fd325a136a57", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Figure(axes=[Axis(label='Frequency', orientation='vertical', scale=LinearScale()), Axis(label='Z values', scal…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig_hist = bplt.figure(padding_y=0.0) # set up a figure object\n", "\n", "# use bqplot's plt interface to plot:\n", "hist = bplt.hist(sample = data[0,0,:],\n", " normalized = False, # normalized=False means we get counts in each bin\n", " bins = 5) \n", "\n", "# change labels\n", "fig_hist.axes[1].label = 'Z values' # xaxes label\n", "fig_hist.axes[0].label = 'Frequency' # yaxes label\n", "\n", "fig_hist # empty plot of x/y" ] }, { "cell_type": "code", "execution_count": 125, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "445cb4d8059c4375853563521b82f6ae", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Label(value=''), HBox(children=(Figure(axes=[ColorAxis(scale=ColorScale(scheme='Reds'), side='t…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig.layout.min_width='500px'\n", "fig_hist.layout.min_width='500px'\n", "\n", "ipywidgets.VBox([mySelectedLabel,\n", " ipywidgets.HBox([fig,fig_hist])])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extras: Interactive histogramming with bqplot\n", "\n", "Now, lets try some interactive histogramming of our buildings data:" ] }, { "cell_type": "code", "execution_count": 126, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "buildings = pd.read_csv(\"https://uiuc-ischool-dataviz.github.io/is445_AOUAOG_fall2021/week02/data/building_inventory.csv\",\n", " na_values = {'Year Acquired': 0, \n", " 'Year Constructed': 0, \n", " 'Square Footage': 0})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create some linear scales and attach axis like before:" ] }, { "cell_type": "code", "execution_count": 127, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9c48837a10b94fcfbc2edbb596c1dd3e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(Figure(axes=[Axis(scale=LinearScale()), Axis(orientation='vertical', scale=LinearScale())], fig…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# since buildings is our data, we don't have to do anything\n", "# with that, but we do need to create our scales and \n", "# axes like we've been doing before:\n", "# (1)\n", "x_sc = bqplot.LinearScale()\n", "y_sc = bqplot.LinearScale()\n", "x_ax = bqplot.Axis(scale = x_sc)\n", "y_ax = bqplot.Axis(scale = y_sc, orientation = 'vertical')\n", "\n", "# (2) now, lets do an interactive rebinning, but lets\n", "# use bqplot and a slider widget to do it\n", "hist = bqplot.Hist(sample = buildings[\"Year Acquired\"],\n", " scales = {'sample': x_sc, 'count': y_sc},\n", " bins = 128, normalized = True,\n", " colors = [\"#FFFFFF\"])\n", "\n", "# lets also create a slider like we've done before\n", "islider = ipywidgets.IntSlider(min = 8, max = 128, step = 1)\n", "# and lets link our sider and our bins of our histogram\n", "ipywidgets.link((islider, 'value'), (hist, 'bins'))\n", "# construct a fig\n", "#fig = bqplot.Figure(marks = [hist], axes = [x_ax, y_ax])\n", "# ***RUN NEXT CELL BEFORE ADDING 2ND HIST\n", "\n", "# (3) ok, but maybe we want to see our original histogram\n", "# underneath, lets add this to our figure\n", "hist2 = bqplot.Hist(sample = buildings[\"Year Acquired\"],\n", " opacity = 0.1, normalized = True,\n", " scales = {'sample': x_sc, 'count': y_sc},\n", " bins = 128)\n", "fig = bqplot.Figure(marks = [hist, hist2], axes = [x_ax, y_ax])\n", "\n", "# for 2 & 3\n", "#display(ipywidgets.VBox([fig, islider]))\n", "ipywidgets.VBox([fig, islider])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extras: Wealth of Nations plot\n", "\n", "See full github repo for all of this data/libraries needed!\n", "\n", "* originially from the TedTalk: https://www.ted.com/talks/hans_rosling_shows_the_best_stats_you_ve_ever_seen\n", "* found on Rosling's website: https://www.ted.com/talks/hans_rosling_shows_the_best_stats_you_ve_ever_seen\n", "* We're going to make a tool similar to GapMinders:https://www.gapminder.org/world/\n", "* Much of this is, in more detail, in the PyGothum-2017 github: https://github.com/dmadeka/PyGotham-2017 \n", "* This will talk to javascript on the backend to mimic the output of another plotting package d3.js, but we don't have to learn about d3.js (just now) and can instead rely on our current Python knowledge" ] }, { "cell_type": "code", "execution_count": 128, "metadata": {}, "outputs": [], "source": [ "# import pandas if we have not\n", "import pandas as pd\n", "\n", "# lets start off our plot at the initial year of 1800\n", "initial_year = 1800" ] }, { "cell_type": "code", "execution_count": 129, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
nameregionincomepopulationlifeExpectancy
0AngolaSub-Saharan Africa[359.93, 359.93, 359.93, 359.93, 359.93, 359.9...[1567028.0, 1567028.0, 1567028.0, 1567028.0, 1...[26.98, 26.98, 26.98, 26.98, 26.98, 26.98, 26....
1BeninSub-Saharan Africa[553.72, 553.72, 553.72, 553.72, 553.72, 553.7...[636559.0, 636559.0, 636559.0, 636559.0, 63655...[31.0, 31.0, 31.0, 31.0, 31.0, 31.0, 31.0, 31....
2BotswanaSub-Saharan Africa[407.36, 407.36, 407.36, 407.36, 407.36, 407.3...[121000.0, 121000.0, 121000.0, 121000.0, 12100...[33.6, 33.6, 33.6, 33.6, 33.6, 33.6, 33.6, 33....
3Burkina FasoSub-Saharan Africa[454.33, 454.33, 454.33, 454.33, 454.33, 454.3...[1665421.0, 1665421.0, 1665421.0, 1665421.0, 1...[29.2, 29.2, 29.2, 29.2, 29.2, 29.2, 29.2, 29....
4BurundiSub-Saharan Africa[447.59, 447.59, 447.59, 447.59, 447.59, 447.5...[899097.0, 899097.0, 899097.0, 899097.0, 89909...[31.5, 31.5, 31.5, 31.5, 31.5, 31.5, 31.5, 31....
..................
174ThailandEast Asia & Pacific[496.98, 496.98, 496.98, 496.98, 496.98, 496.9...[4665000.0, 4665000.0, 4665000.0, 4665000.0, 4...[30.4, 30.4, 30.4, 30.4, 30.4, 30.4, 30.4, 30....
175Timor-LesteEast Asia & Pacific[514.12, 514.3505, 514.581, 514.8115, 515.042,...[137262.0, 137262.0, 137262.0, 137262.0, 13726...[28.97, 28.97, 28.97, 28.97, 28.97, 28.97, 28....
177TongaEast Asia & Pacific[667.71, 667.71, 667.71, 667.71, 667.71, 667.7...[18658.0, 18654.325581395347, 18650.6511627907...[57.91, 57.91, 57.91, 57.91, 57.91, 57.91, 57....
178VietnamEast Asia & Pacific[459.71, 459.71, 459.71, 459.71, 459.71, 459.7...[6551000.0, 6551000.0, 6551000.0, 6551000.0, 6...[32.0, 32.0, 32.0, 32.0, 32.0, 32.0, 32.0, 32....
179VanuatuEast Asia & Pacific[829.58, 829.58, 829.58, 829.58, 829.58, 829.5...[27791.0, 27791.0, 27791.0, 27791.0, 27791.0, ...[40.8, 40.8, 40.8, 40.8, 40.8, 40.8, 40.8, 40....
\n", "

178 rows × 5 columns

\n", "
" ], "text/plain": [ " name region \\\n", "0 Angola Sub-Saharan Africa \n", "1 Benin Sub-Saharan Africa \n", "2 Botswana Sub-Saharan Africa \n", "3 Burkina Faso Sub-Saharan Africa \n", "4 Burundi Sub-Saharan Africa \n", ".. ... ... \n", "174 Thailand East Asia & Pacific \n", "175 Timor-Leste East Asia & Pacific \n", "177 Tonga East Asia & Pacific \n", "178 Vietnam East Asia & Pacific \n", "179 Vanuatu East Asia & Pacific \n", "\n", " income \\\n", "0 [359.93, 359.93, 359.93, 359.93, 359.93, 359.9... \n", "1 [553.72, 553.72, 553.72, 553.72, 553.72, 553.7... \n", "2 [407.36, 407.36, 407.36, 407.36, 407.36, 407.3... \n", "3 [454.33, 454.33, 454.33, 454.33, 454.33, 454.3... \n", "4 [447.59, 447.59, 447.59, 447.59, 447.59, 447.5... \n", ".. ... \n", "174 [496.98, 496.98, 496.98, 496.98, 496.98, 496.9... \n", "175 [514.12, 514.3505, 514.581, 514.8115, 515.042,... \n", "177 [667.71, 667.71, 667.71, 667.71, 667.71, 667.7... \n", "178 [459.71, 459.71, 459.71, 459.71, 459.71, 459.7... \n", "179 [829.58, 829.58, 829.58, 829.58, 829.58, 829.5... \n", "\n", " population \\\n", "0 [1567028.0, 1567028.0, 1567028.0, 1567028.0, 1... \n", "1 [636559.0, 636559.0, 636559.0, 636559.0, 63655... \n", "2 [121000.0, 121000.0, 121000.0, 121000.0, 12100... \n", "3 [1665421.0, 1665421.0, 1665421.0, 1665421.0, 1... \n", "4 [899097.0, 899097.0, 899097.0, 899097.0, 89909... \n", ".. ... \n", "174 [4665000.0, 4665000.0, 4665000.0, 4665000.0, 4... \n", "175 [137262.0, 137262.0, 137262.0, 137262.0, 13726... \n", "177 [18658.0, 18654.325581395347, 18650.6511627907... \n", "178 [6551000.0, 6551000.0, 6551000.0, 6551000.0, 6... \n", "179 [27791.0, 27791.0, 27791.0, 27791.0, 27791.0, ... \n", "\n", " lifeExpectancy \n", "0 [26.98, 26.98, 26.98, 26.98, 26.98, 26.98, 26.... \n", "1 [31.0, 31.0, 31.0, 31.0, 31.0, 31.0, 31.0, 31.... \n", "2 [33.6, 33.6, 33.6, 33.6, 33.6, 33.6, 33.6, 33.... \n", "3 [29.2, 29.2, 29.2, 29.2, 29.2, 29.2, 29.2, 29.... \n", "4 [31.5, 31.5, 31.5, 31.5, 31.5, 31.5, 31.5, 31.... \n", ".. ... \n", "174 [30.4, 30.4, 30.4, 30.4, 30.4, 30.4, 30.4, 30.... \n", "175 [28.97, 28.97, 28.97, 28.97, 28.97, 28.97, 28.... \n", "177 [57.91, 57.91, 57.91, 57.91, 57.91, 57.91, 57.... \n", "178 [32.0, 32.0, 32.0, 32.0, 32.0, 32.0, 32.0, 32.... \n", "179 [40.8, 40.8, 40.8, 40.8, 40.8, 40.8, 40.8, 40.... \n", "\n", "[178 rows x 5 columns]" ] }, "execution_count": 129, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# we'll read in our datafile and apply \n", "# some pre-written cleaning routines \n", "# get out the data we want for our plotting\n", "# put this file in the same directory as the notebook, or link it in your path:\n", "from sys import path\n", "path.append('./library') # this is the subdirectory where wealth_of_nations.py is\n", "from wealth_of_nations import process_data, get_min_max, get_data\n", "\n", "# grab data\n", "data = process_data('./library/nations.json')\n", "\n", "data" ] }, { "cell_type": "code", "execution_count": 130, "metadata": {}, "outputs": [], "source": [ "# grab min & max values of our variables of interest\n", "income_min, income_max, life_exp_min, life_exp_max, pop_min, pop_max = get_min_max(data)" ] }, { "cell_type": "code", "execution_count": 131, "metadata": {}, "outputs": [], "source": [ "# lets allow for a mouse-over interaction\n", "# for silly:\n", "import bqplot\n", "tt = bqplot.Tooltip(fields=['name', 'x', 'y'], \n", " labels=['Country Name', \n", " 'Income per Capita', 'Life Expectancy'])\n", "#bqplot.Tooltip?" ] }, { "cell_type": "code", "execution_count": 132, "metadata": {}, "outputs": [], "source": [ "# we will label what year is being plotted, just like in the Gabminder plot\n", "year_label = bqplot.Label(x=[0.75], y=[0.10], \n", " font_size=52, font_weight='bolder', \n", " colors=['orange'],\n", " text=[str(initial_year)], enable_move=True)" ] }, { "cell_type": "code", "execution_count": 133, "metadata": {}, "outputs": [], "source": [ "# we'll define our scales like before\n", "# here we scale our x & y axis to the scales of the min and max of our data\n", "x_sc = bqplot.LogScale(min=income_min, max=income_max)\n", "y_sc = bqplot.LinearScale(min=life_exp_min, max=life_exp_max)\n", "\n", "# this is just something to color-code each circle by the region it corresponds to\n", "# (for example, asia, south america, africa, etc)\n", "# the colors call is just mapping each catagorical variable to a color\n", "c_sc = bqplot.OrdinalColorScale(domain=data['region'].unique().tolist(), \n", " colors=bqplot.CATEGORY10[:6])\n", "\n", "# finally, we want the size of each of our dots to correspond to the population of \n", "# each country\n", "#size_sc = bqplot.LinearScale(min=pop_min, max=pop_max)#, mid_range=0.1)\n", "size_sc = bqplot.LinearScale(max=1326856173.0, min=2128.0)\n", "#bqplot.LinearScale?" ] }, { "cell_type": "code", "execution_count": 134, "metadata": {}, "outputs": [], "source": [ "# create and label our x & y axis\n", "ax_y = bqplot.Axis(label='Life Expectancy', scale=y_sc, \n", " orientation='vertical', side='left', \n", " grid_lines='solid')\n", "ax_x = bqplot.Axis(label='Income per Capita', scale=x_sc, \n", " grid_lines='solid')" ] }, { "cell_type": "code", "execution_count": 135, "metadata": {}, "outputs": [], "source": [ "# now we'll use another little function from our library above to grab\n", "# data for our initial setup (year = 1800)\n", "# Start with the first year's data\n", "cap_income, life_exp, pop = get_data(data,initial_year,initial_year)" ] }, { "cell_type": "code", "execution_count": 136, "metadata": {}, "outputs": [ { "ename": "KeyError", "evalue": "27", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/pandas/core/indexes/base.py:3805\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3804\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 3805\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_engine\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcasted_key\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3806\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n", "File \u001b[0;32mindex.pyx:167\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", "File \u001b[0;32mindex.pyx:196\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", "File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:2606\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.Int64HashTable.get_item\u001b[0;34m()\u001b[0m\n", "File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:2630\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.Int64HashTable.get_item\u001b[0;34m()\u001b[0m\n", "\u001b[0;31mKeyError\u001b[0m: 27", "\nThe above exception was the direct cause of the following exception:\n", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/ipywidgets/widgets/widget.py:757\u001b[0m, in \u001b[0;36mWidget._handle_msg\u001b[0;34m(self, msg)\u001b[0m\n\u001b[1;32m 755\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbuffer_paths\u001b[39m\u001b[38;5;124m'\u001b[39m \u001b[38;5;129;01min\u001b[39;00m data:\n\u001b[1;32m 756\u001b[0m _put_buffers(state, data[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbuffer_paths\u001b[39m\u001b[38;5;124m'\u001b[39m], msg[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mbuffers\u001b[39m\u001b[38;5;124m'\u001b[39m])\n\u001b[0;32m--> 757\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset_state\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstate\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 759\u001b[0m \u001b[38;5;66;03m# Handle a state request.\u001b[39;00m\n\u001b[1;32m 760\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m method \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrequest_state\u001b[39m\u001b[38;5;124m'\u001b[39m:\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/ipywidgets/widgets/widget.py:621\u001b[0m, in \u001b[0;36mWidget.set_state\u001b[0;34m(self, sync_data)\u001b[0m\n\u001b[1;32m 616\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_send(msg, buffers\u001b[38;5;241m=\u001b[39mecho_buffers)\n\u001b[1;32m 618\u001b[0m \u001b[38;5;66;03m# The order of these context managers is important. Properties must\u001b[39;00m\n\u001b[1;32m 619\u001b[0m \u001b[38;5;66;03m# be locked when the hold_trait_notification context manager is\u001b[39;00m\n\u001b[1;32m 620\u001b[0m \u001b[38;5;66;03m# released and notifications are fired.\u001b[39;00m\n\u001b[0;32m--> 621\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_lock_property(\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39msync_data), \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhold_trait_notifications():\n\u001b[1;32m 622\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m name \u001b[38;5;129;01min\u001b[39;00m sync_data:\n\u001b[1;32m 623\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m name \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mkeys:\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/contextlib.py:142\u001b[0m, in \u001b[0;36m_GeneratorContextManager.__exit__\u001b[0;34m(self, typ, value, traceback)\u001b[0m\n\u001b[1;32m 140\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m typ \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 141\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 142\u001b[0m \u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgen\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 143\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m:\n\u001b[1;32m 144\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/traitlets/traitlets.py:1510\u001b[0m, in \u001b[0;36mHasTraits.hold_trait_notifications\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1508\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m changes \u001b[38;5;129;01min\u001b[39;00m cache\u001b[38;5;241m.\u001b[39mvalues():\n\u001b[1;32m 1509\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m change \u001b[38;5;129;01min\u001b[39;00m changes:\n\u001b[0;32m-> 1510\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnotify_change\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchange\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/ipywidgets/widgets/widget.py:687\u001b[0m, in \u001b[0;36mWidget.notify_change\u001b[0;34m(self, change)\u001b[0m\n\u001b[1;32m 684\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m name \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mkeys \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_should_send_property(name, \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, name)):\n\u001b[1;32m 685\u001b[0m \u001b[38;5;66;03m# Send new state to front-end\u001b[39;00m\n\u001b[1;32m 686\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msend_state(key\u001b[38;5;241m=\u001b[39mname)\n\u001b[0;32m--> 687\u001b[0m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mWidget\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnotify_change\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchange\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/traitlets/traitlets.py:1525\u001b[0m, in \u001b[0;36mHasTraits.notify_change\u001b[0;34m(self, change)\u001b[0m\n\u001b[1;32m 1523\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mnotify_change\u001b[39m(\u001b[38;5;28mself\u001b[39m, change: Bunch) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 1524\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Notify observers of a change event\"\"\"\u001b[39;00m\n\u001b[0;32m-> 1525\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_notify_observers\u001b[49m\u001b[43m(\u001b[49m\u001b[43mchange\u001b[49m\u001b[43m)\u001b[49m\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/traitlets/traitlets.py:1568\u001b[0m, in \u001b[0;36mHasTraits._notify_observers\u001b[0;34m(self, event)\u001b[0m\n\u001b[1;32m 1565\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(c, EventHandler) \u001b[38;5;129;01mand\u001b[39;00m c\u001b[38;5;241m.\u001b[39mname \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 1566\u001b[0m c \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, c\u001b[38;5;241m.\u001b[39mname)\n\u001b[0;32m-> 1568\u001b[0m \u001b[43mc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mevent\u001b[49m\u001b[43m)\u001b[49m\n", "Cell \u001b[0;32mIn[142], line 7\u001b[0m, in \u001b[0;36mhover_changed\u001b[0;34m(change)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mhover_changed\u001b[39m(change):\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m change\u001b[38;5;241m.\u001b[39mnew \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m----> 7\u001b[0m nation_line\u001b[38;5;241m.\u001b[39mx \u001b[38;5;241m=\u001b[39m \u001b[43mdata\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mincome\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[43mchange\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnew\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[1;32m 8\u001b[0m nation_line\u001b[38;5;241m.\u001b[39my \u001b[38;5;241m=\u001b[39m data[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mlifeExpectancy\u001b[39m\u001b[38;5;124m'\u001b[39m][change\u001b[38;5;241m.\u001b[39mnew \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m]\n\u001b[1;32m 9\u001b[0m nation_line\u001b[38;5;241m.\u001b[39mvisible \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/pandas/core/series.py:1121\u001b[0m, in \u001b[0;36mSeries.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 1118\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_values[key]\n\u001b[1;32m 1120\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m key_is_scalar:\n\u001b[0;32m-> 1121\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_value\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1123\u001b[0m \u001b[38;5;66;03m# Convert generator to list before going through hashable part\u001b[39;00m\n\u001b[1;32m 1124\u001b[0m \u001b[38;5;66;03m# (We will iterate through the generator there to check for slices)\u001b[39;00m\n\u001b[1;32m 1125\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_iterator(key):\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/pandas/core/series.py:1237\u001b[0m, in \u001b[0;36mSeries._get_value\u001b[0;34m(self, label, takeable)\u001b[0m\n\u001b[1;32m 1234\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_values[label]\n\u001b[1;32m 1236\u001b[0m \u001b[38;5;66;03m# Similar to Index.get_value, but we do not fall back to positional\u001b[39;00m\n\u001b[0;32m-> 1237\u001b[0m loc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlabel\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1239\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_integer(loc):\n\u001b[1;32m 1240\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_values[loc]\n", "File \u001b[0;32m/opt/anaconda3/envs/DataViz2/lib/python3.10/site-packages/pandas/core/indexes/base.py:3812\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 3807\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(casted_key, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m (\n\u001b[1;32m 3808\u001b[0m \u001b[38;5;28misinstance\u001b[39m(casted_key, abc\u001b[38;5;241m.\u001b[39mIterable)\n\u001b[1;32m 3809\u001b[0m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28many\u001b[39m(\u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m casted_key)\n\u001b[1;32m 3810\u001b[0m ):\n\u001b[1;32m 3811\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m InvalidIndexError(key)\n\u001b[0;32m-> 3812\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n\u001b[1;32m 3813\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[1;32m 3814\u001b[0m \u001b[38;5;66;03m# If we have a listlike key, _check_indexing_error will raise\u001b[39;00m\n\u001b[1;32m 3815\u001b[0m \u001b[38;5;66;03m# InvalidIndexError. Otherwise we fall through and re-raise\u001b[39;00m\n\u001b[1;32m 3816\u001b[0m \u001b[38;5;66;03m# the TypeError.\u001b[39;00m\n\u001b[1;32m 3817\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_indexing_error(key)\n", "\u001b[0;31mKeyError\u001b[0m: 27" ] } ], "source": [ "# now lets make our scatter plot!\n", "wealth_scat = bqplot.Scatter(x=cap_income, y=life_exp, \n", " color=data['region'], size=pop,\n", " names=data['name'], display_names=False,\n", " scales={'x': x_sc, 'y': y_sc, 'color': c_sc, \n", " 'size': size_sc},\n", " default_size=4112, tooltip=tt, \n", " animate=True, stroke='Black',\n", " unhovered_style={'opacity': 0.5})\n", "# much of these calls are things we've seen before, others will allow fun things \n", "# like animation and also the ability to click on our plot and interact with it" ] }, { "cell_type": "code", "execution_count": 137, "metadata": {}, "outputs": [], "source": [ "# for our initial, 1800 view, we'll just allow the first \"line\" of the evolution of the \n", "# each nation's track to be displayed... this is essentially a place holder (visible = false)\n", "nation_line = bqplot.Lines(x=data['income'][0], \n", " y=data['lifeExpectancy'][0], \n", " colors=['Gray'],\n", " scales={'x': x_sc, 'y': y_sc}, visible=False)" ] }, { "cell_type": "code", "execution_count": 138, "metadata": {}, "outputs": [], "source": [ "# milliseconds of time between changes we make\n", "time_interval = 10" ] }, { "cell_type": "code", "execution_count": 139, "metadata": {}, "outputs": [], "source": [ "# create the figure & \n", "fig = bqplot.Figure(marks=[wealth_scat, year_label, nation_line], \n", " axes=[ax_x, ax_y],\n", " title='Health and Wealth of Nations', \n", " animation_duration=time_interval)\n", "\n", "# lets control the size in pixels too\n", "fig.layout.min_width = '960px'\n", "fig.layout.min_height = '640px'" ] }, { "cell_type": "code", "execution_count": 140, "metadata": {}, "outputs": [], "source": [ "# we'll use our friend the int slider to slide through years\n", "# for silly:\n", "import ipywidgets\n", "year_slider = ipywidgets.IntSlider(min=1800, max=2008, step=1, description='Year', value=initial_year)" ] }, { "cell_type": "code", "execution_count": 141, "metadata": {}, "outputs": [], "source": [ "# make sure we define what happens when we change the year on our slider\n", "def year_changed(change):\n", " wealth_scat.x, wealth_scat.y, wealth_scat.size = get_data(data,year_slider.value,initial_year)\n", " #wealth_scat.size+=1000\n", " year_label.text = [str(year_slider.value)]\n", "\n", "year_slider.observe(year_changed, 'value')" ] }, { "cell_type": "code", "execution_count": 142, "metadata": {}, "outputs": [], "source": [ "# now we'll say what happens when we hover over an object\n", "# we'll use \"change\" again to make it such that if a \n", "# user hovers over a country, the countries \"life line\" \n", "# is visible\n", "def hover_changed(change):\n", " if change.new is not None:\n", " nation_line.x = data['income'][change.new + 1]\n", " nation_line.y = data['lifeExpectancy'][change.new + 1]\n", " nation_line.visible = True\n", " else:\n", " nation_line.visible = False\n", " \n", "wealth_scat.observe(hover_changed, 'hovered_point')" ] }, { "cell_type": "code", "execution_count": 143, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Link(source=(Play(value=1800, interval=10, max=2008, min=1800), 'value'), target=(IntSlider(value=1800, descri…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# finally, lets add a little play button so we can animate\n", "# what happens in time, just like on the d3.js plot\n", "play_button = ipywidgets.Play(min=1800, max=2008, interval=time_interval)\n", "# note, we use \"jslink\" because the \"backend\" here is javascript\n", "# bqplot is just interacting with javascript\n", "ipywidgets.jslink((play_button, 'value'), (year_slider, 'value'))" ] }, { "cell_type": "code", "execution_count": 144, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "cca05e75c66a488f95350cb0a90f39aa", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox(children=(HBox(children=(Play(value=1800, interval=10, max=2008, min=1800), IntSlider(value=1800, descrip…" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# finally, lets put it all together!!\n", "\n", "ipywidgets.VBox([ipywidgets.HBox([play_button, year_slider]), fig])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "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.14" } }, "nbformat": 4, "nbformat_minor": 2 }