1&&\"string\"==typeof v&&!d.checkClone&&De.test(v))return e.each((function(i){var o=e.eq(i);y&&(t[0]=v.call(this,i,o.html())),Re(o,t,n,r)}));if(p&&(a=(i=xe(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=a),a||r)){for(u=(s=b.map(ve(i,\"script\"),Le)).length;f0&&ye(a,!u&&ve(e,\"script\")),s},cleanData:function(e){for(var t,n,r,i=b.event.special,o=0;void 0!==(n=e[o]);o++)if(X(n)){if(t=n[G.expando]){if(t.events)for(r in t.events)i[r]?b.event.remove(n,r):b.removeEvent(n,r,t.handle);n[G.expando]=void 0}n[Y.expando]&&(n[Y.expando]=void 0)}}}),b.fn.extend({detach:function(e){return Me(this,e,!0)},remove:function(e){return Me(this,e)},text:function(e){return B(this,(function(e){return void 0===e?b.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)}))}),null,e,arguments.length)},append:function(){return Re(this,arguments,(function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)}))},prepend:function(){return Re(this,arguments,(function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}}))},before:function(){return Re(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this)}))},after:function(){return Re(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)}))},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(b.cleanData(ve(e,!1)),e.textContent=\"\");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map((function(){return b.clone(this,e,t)}))},html:function(e){return B(this,(function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if(\"string\"==typeof e&&!Ne.test(e)&&!ge[(de.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=b.htmlPrefilter(e);try{for(;n3,ne.removeChild(t)),s}}))}();var ze=[\"Webkit\",\"Moz\",\"ms\"],Ue=v.createElement(\"div\").style,Xe={};function Ve(e){var t=b.cssProps[e]||Xe[e];return t||(e in Ue?e:Xe[e]=function(e){for(var t=e[0].toUpperCase()+e.slice(1),n=ze.length;n--;)if((e=ze[n]+t)in Ue)return e}(e)||e)}var Ge=/^(none|table(?!-c[ea]).+)/,Ye=/^--/,Qe={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Je={letterSpacing:\"0\",fontWeight:\"400\"};function Ke(e,t,n){var r=ee.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||\"px\"):t}function Ze(e,t,n,r,i,o){var a=\"width\"===t?1:0,s=0,u=0;if(n===(r?\"border\":\"content\"))return 0;for(;a<4;a+=2)\"margin\"===n&&(u+=b.css(e,n+te[a],!0,i)),r?(\"content\"===n&&(u-=b.css(e,\"padding\"+te[a],!0,i)),\"margin\"!==n&&(u-=b.css(e,\"border\"+te[a]+\"Width\",!0,i))):(u+=b.css(e,\"padding\"+te[a],!0,i),\"padding\"!==n?u+=b.css(e,\"border\"+te[a]+\"Width\",!0,i):s+=b.css(e,\"border\"+te[a]+\"Width\",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function et(e,t,n){var r=We(e),i=(!d.boxSizingReliable()||n)&&\"border-box\"===b.css(e,\"boxSizing\",!1,r),o=i,a=$e(e,t,r),s=\"offset\"+t[0].toUpperCase()+t.slice(1);if(Ie.test(a)){if(!n)return a;a=\"auto\"}return(!d.boxSizingReliable()&&i||!d.reliableTrDimensions()&&k(e,\"tr\")||\"auto\"===a||!parseFloat(a)&&\"inline\"===b.css(e,\"display\",!1,r))&&e.getClientRects().length&&(i=\"border-box\"===b.css(e,\"boxSizing\",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ze(e,t,n||(i?\"border\":\"content\"),o,r,a)+\"px\"}function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=$e(e,\"opacity\");return\"\"===n?\"1\":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=U(t),u=Ye.test(t),l=e.style;if(u||(t=Ve(s)),a=b.cssHooks[t]||b.cssHooks[s],void 0===n)return a&&\"get\"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];\"string\"===(o=typeof n)&&(i=ee.exec(n))&&i[1]&&(n=ae(e,t,i),o=\"number\"),null!=n&&n==n&&(\"number\"!==o||u||(n+=i&&i[3]||(b.cssNumber[s]?\"\":\"px\")),d.clearCloneStyle||\"\"!==n||0!==t.indexOf(\"background\")||(l[t]=\"inherit\"),a&&\"set\"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=U(t);return Ye.test(t)||(t=Ve(s)),(a=b.cssHooks[t]||b.cssHooks[s])&&\"get\"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=$e(e,t,r)),\"normal\"===i&&t in Je&&(i=Je[t]),\"\"===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),b.each([\"height\",\"width\"],(function(e,t){b.cssHooks[t]={get:function(e,n,r){if(n)return!Ge.test(b.css(e,\"display\"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):Fe(e,Qe,(function(){return et(e,t,r)}))},set:function(e,n,r){var i,o=We(e),a=!d.scrollboxSize()&&\"absolute\"===o.position,s=(a||r)&&\"border-box\"===b.css(e,\"boxSizing\",!1,o),u=r?Ze(e,t,r,s,o):0;return s&&a&&(u-=Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,\"border\",!1,o)-.5)),u&&(i=ee.exec(n))&&\"px\"!==(i[3]||\"px\")&&(e.style[t]=n,n=b.css(e,t)),Ke(0,n,u)}}})),b.cssHooks.marginLeft=_e(d.reliableMarginLeft,(function(e,t){if(t)return(parseFloat($e(e,\"marginLeft\"))||e.getBoundingClientRect().left-Fe(e,{marginLeft:0},(function(){return e.getBoundingClientRect().left})))+\"px\"})),b.each({margin:\"\",padding:\"\",border:\"Width\"},(function(e,t){b.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o=\"string\"==typeof n?n.split(\" \"):[n];r<4;r++)i[e+te[r]+t]=o[r]||o[r-2]||o[0];return i}},\"margin\"!==e&&(b.cssHooks[e+t].set=Ke)})),b.fn.extend({css:function(e,t){return B(this,(function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=We(e),i=t.length;a1)}}),b.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||b.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(b.cssNumber[n]?\"\":\"px\")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=b.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=b.css(e.elem,e.prop,\"\"))&&\"auto\"!==t?t:0},set:function(e){b.fx.step[e.prop]?b.fx.step[e.prop](e):1!==e.elem.nodeType||!b.cssHooks[e.prop]&&null==e.elem.style[Ve(e.prop)]?e.elem[e.prop]=e.now:b.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},b.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:\"swing\"},b.fx=tt.prototype.init,b.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===v.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,b.fx.interval),b.fx.tick())}function st(){return e.setTimeout((function(){nt=void 0})),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i[\"margin\"+(n=te[r])]=i[\"padding\"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(ct.tweeners[t]||[]).concat(ct.tweeners[\"*\"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each((function(){b.removeAttr(this,e)}))}}),b.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?b.prop(e,t,n):(1===o&&b.isXMLDoc(e)||(i=b.attrHooks[t.toLowerCase()]||(b.expr.match.bool.test(t)?ft:void 0)),void 0!==n?null===n?void b.removeAttr(e,t):i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+\"\"),n):i&&\"get\"in i&&null!==(r=i.get(e,t))?r:null==(r=b.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!d.radioValue&&\"radio\"===t&&k(e,\"input\")){var n=e.value;return e.setAttribute(\"type\",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(O);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),ft={set:function(e,t,n){return!1===t?b.removeAttr(e,n):e.setAttribute(n,n),n}},b.each(b.expr.match.bool.source.match(/\\w+/g),(function(e,t){var n=pt[t]||b.find.attr;pt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=pt[a],pt[a]=i,i=null!=n(e,t,r)?a:null,pt[a]=o),i}}));var dt=/^(?:input|select|textarea|button)$/i,ht=/^(?:a|area)$/i;function gt(e){return(e.match(O)||[]).join(\" \")}function vt(e){return e.getAttribute&&e.getAttribute(\"class\")||\"\"}function yt(e){return Array.isArray(e)?e:\"string\"==typeof e&&e.match(O)||[]}b.fn.extend({prop:function(e,t){return B(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each((function(){delete this[b.propFix[e]||e]}))}}),b.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&b.isXMLDoc(e)||(t=b.propFix[t]||t,i=b.propHooks[t]),void 0!==n?i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&\"get\"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=b.find.attr(e,\"tabindex\");return t?parseInt(t,10):dt.test(e.nodeName)||ht.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:\"htmlFor\",class:\"className\"}}),d.optSelected||(b.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),b.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],(function(){b.propFix[this.toLowerCase()]=this})),b.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(h(e))return this.each((function(t){b(this).addClass(e.call(this,t,vt(this)))}));if((t=yt(e)).length)for(;n=this[u++];)if(i=vt(n),r=1===n.nodeType&&\" \"+gt(i)+\" \"){for(a=0;o=t[a++];)r.indexOf(\" \"+o+\" \")<0&&(r+=o+\" \");i!==(s=gt(r))&&n.setAttribute(\"class\",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(h(e))return this.each((function(t){b(this).removeClass(e.call(this,t,vt(this)))}));if(!arguments.length)return this.attr(\"class\",\"\");if((t=yt(e)).length)for(;n=this[u++];)if(i=vt(n),r=1===n.nodeType&&\" \"+gt(i)+\" \"){for(a=0;o=t[a++];)for(;r.indexOf(\" \"+o+\" \")>-1;)r=r.replace(\" \"+o+\" \",\" \");i!==(s=gt(r))&&n.setAttribute(\"class\",s)}return this},toggleClass:function(e,t){var n=typeof e,r=\"string\"===n||Array.isArray(e);return\"boolean\"==typeof t&&r?t?this.addClass(e):this.removeClass(e):h(e)?this.each((function(n){b(this).toggleClass(e.call(this,n,vt(this),t),t)})):this.each((function(){var t,i,o,a;if(r)for(i=0,o=b(this),a=yt(e);t=a[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&\"boolean\"!==n||((t=vt(this))&&G.set(this,\"__className__\",t),this.setAttribute&&this.setAttribute(\"class\",t||!1===e?\"\":G.get(this,\"__className__\")||\"\"))}))},hasClass:function(e){var t,n,r=0;for(t=\" \"+e+\" \";n=this[r++];)if(1===n.nodeType&&(\" \"+gt(vt(n))+\" \").indexOf(t)>-1)return!0;return!1}});var mt=/\\r/g;b.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=h(e),this.each((function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,b(this).val()):e)?i=\"\":\"number\"==typeof i?i+=\"\":Array.isArray(i)&&(i=b.map(i,(function(e){return null==e?\"\":e+\"\"}))),(t=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()])&&\"set\"in t&&void 0!==t.set(this,i,\"value\")||(this.value=i))}))):i?(t=b.valHooks[i.type]||b.valHooks[i.nodeName.toLowerCase()])&&\"get\"in t&&void 0!==(n=t.get(i,\"value\"))?n:\"string\"==typeof(n=i.value)?n.replace(mt,\"\"):null==n?\"\":n:void 0}}),b.extend({valHooks:{option:{get:function(e){var t=b.find.attr(e,\"value\");return null!=t?t:gt(b.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a=\"select-one\"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),b.each([\"radio\",\"checkbox\"],(function(){b.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=b.inArray(b(e).val(),t)>-1}},d.checkOn||(b.valHooks[this].get=function(e){return null===e.getAttribute(\"value\")?\"on\":e.value})})),d.focusin=\"onfocusin\"in e;var xt=/^(?:focusinfocus|focusoutblur)$/,bt=function(e){e.stopPropagation()};b.extend(b.event,{trigger:function(t,n,r,i){var o,a,s,u,l,f,p,d,y=[r||v],m=c.call(t,\"type\")?t.type:t,x=c.call(t,\"namespace\")?t.namespace.split(\".\"):[];if(a=d=s=r=r||v,3!==r.nodeType&&8!==r.nodeType&&!xt.test(m+b.event.triggered)&&(m.indexOf(\".\")>-1&&(x=m.split(\".\"),m=x.shift(),x.sort()),l=m.indexOf(\":\")<0&&\"on\"+m,(t=t[b.expando]?t:new b.Event(m,\"object\"==typeof t&&t)).isTrigger=i?2:3,t.namespace=x.join(\".\"),t.rnamespace=t.namespace?new RegExp(\"(^|\\\\.)\"+x.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:b.makeArray(n,[t]),p=b.event.special[m]||{},i||!p.trigger||!1!==p.trigger.apply(r,n))){if(!i&&!p.noBubble&&!g(r)){for(u=p.delegateType||m,xt.test(u+m)||(a=a.parentNode);a;a=a.parentNode)y.push(a),s=a;s===(r.ownerDocument||v)&&y.push(s.defaultView||s.parentWindow||e)}for(o=0;(a=y[o++])&&!t.isPropagationStopped();)d=a,t.type=o>1?u:p.bindType||m,(f=(G.get(a,\"events\")||Object.create(null))[t.type]&&G.get(a,\"handle\"))&&f.apply(a,n),(f=l&&a[l])&&f.apply&&X(a)&&(t.result=f.apply(a,n),!1===t.result&&t.preventDefault());return t.type=m,i||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(y.pop(),n)||!X(r)||l&&h(r[m])&&!g(r)&&((s=r[l])&&(r[l]=null),b.event.triggered=m,t.isPropagationStopped()&&d.addEventListener(m,bt),r[m](),t.isPropagationStopped()&&d.removeEventListener(m,bt),b.event.triggered=void 0,s&&(r[l]=s)),t.result}},simulate:function(e,t,n){var r=b.extend(new b.Event,n,{type:e,isSimulated:!0});b.event.trigger(r,null,t)}}),b.fn.extend({trigger:function(e,t){return this.each((function(){b.event.trigger(e,t,this)}))},triggerHandler:function(e,t){var n=this[0];if(n)return b.event.trigger(e,t,n,!0)}}),d.focusin||b.each({focus:\"focusin\",blur:\"focusout\"},(function(e,t){var n=function(e){b.event.simulate(t,e.target,b.event.fix(e))};b.event.special[t]={setup:function(){var r=this.ownerDocument||this.document||this,i=G.access(r,t);i||r.addEventListener(e,n,!0),G.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this.document||this,i=G.access(r,t)-1;i?G.access(r,t,i):(r.removeEventListener(e,n,!0),G.remove(r,t))}}}));var wt=e.location,Tt={guid:Date.now()},Ct=/\\?/;b.parseXML=function(t){var n;if(!t||\"string\"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,\"text/xml\")}catch(e){n=void 0}return n&&!n.getElementsByTagName(\"parsererror\").length||b.error(\"Invalid XML: \"+t),n};var Et=/\\[\\]$/,St=/\\r?\\n/g,kt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function Nt(e,t,n,r){var i;if(Array.isArray(t))b.each(t,(function(t,i){n||Et.test(e)?r(e,i):Nt(e+\"[\"+(\"object\"==typeof i&&null!=i?t:\"\")+\"]\",i,n,r)}));else if(n||\"object\"!==x(t))r(e,t);else for(i in t)Nt(e+\"[\"+i+\"]\",t[i],n,r)}b.param=function(e,t){var n,r=[],i=function(e,t){var n=h(t)?t():t;r[r.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(null==n?\"\":n)};if(null==e)return\"\";if(Array.isArray(e)||e.jquery&&!b.isPlainObject(e))b.each(e,(function(){i(this.name,this.value)}));else for(n in e)Nt(n,e[n],t,i);return r.join(\"&\")},b.fn.extend({serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var e=b.prop(this,\"elements\");return e?b.makeArray(e):this})).filter((function(){var e=this.type;return this.name&&!b(this).is(\":disabled\")&&At.test(this.nodeName)&&!kt.test(e)&&(this.checked||!pe.test(e))})).map((function(e,t){var n=b(this).val();return null==n?null:Array.isArray(n)?b.map(n,(function(e){return{name:t.name,value:e.replace(St,\"\\r\\n\")}})):{name:t.name,value:n.replace(St,\"\\r\\n\")}})).get()}});var Dt=/%20/g,jt=/#.*$/,qt=/([?&])_=[^&]*/,Lt=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,Ht=/^(?:GET|HEAD)$/,Ot=/^\\/\\//,Pt={},Rt={},Mt=\"*/\".concat(\"*\"),It=v.createElement(\"a\");function Wt(e){return function(t,n){\"string\"!=typeof t&&(n=t,t=\"*\");var r,i=0,o=t.toLowerCase().match(O)||[];if(h(n))for(;r=o[i++];)\"+\"===r[0]?(r=r.slice(1)||\"*\",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Ft(e,t,n,r){var i={},o=e===Rt;function a(s){var u;return i[s]=!0,b.each(e[s]||[],(function(e,s){var l=s(t,n,r);return\"string\"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)})),u}return a(t.dataTypes[0])||!i[\"*\"]&&a(\"*\")}function Bt(e,t){var n,r,i=b.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&b.extend(!0,e,r),e}It.href=wt.href,b.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:wt.href,type:\"GET\",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(wt.protocol),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":Mt,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":JSON.parse,\"text xml\":b.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Bt(Bt(e,b.ajaxSettings),t):Bt(b.ajaxSettings,e)},ajaxPrefilter:Wt(Pt),ajaxTransport:Wt(Rt),ajax:function(t,n){\"object\"==typeof t&&(n=t,t=void 0),n=n||{};var r,i,o,a,s,u,l,c,f,p,d=b.ajaxSetup({},n),h=d.context||d,g=d.context&&(h.nodeType||h.jquery)?b(h):b.event,y=b.Deferred(),m=b.Callbacks(\"once memory\"),x=d.statusCode||{},w={},T={},C=\"canceled\",E={readyState:0,getResponseHeader:function(e){var t;if(l){if(!a)for(a={};t=Lt.exec(o);)a[t[1].toLowerCase()+\" \"]=(a[t[1].toLowerCase()+\" \"]||[]).concat(t[2]);t=a[e.toLowerCase()+\" \"]}return null==t?null:t.join(\", \")},getAllResponseHeaders:function(){return l?o:null},setRequestHeader:function(e,t){return null==l&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,w[e]=t),this},overrideMimeType:function(e){return null==l&&(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(l)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return r&&r.abort(t),S(0,t),this}};if(y.promise(E),d.url=((t||d.url||wt.href)+\"\").replace(Ot,wt.protocol+\"//\"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=(d.dataType||\"*\").toLowerCase().match(O)||[\"\"],null==d.crossDomain){u=v.createElement(\"a\");try{u.href=d.url,u.href=u.href,d.crossDomain=It.protocol+\"//\"+It.host!=u.protocol+\"//\"+u.host}catch(e){d.crossDomain=!0}}if(d.data&&d.processData&&\"string\"!=typeof d.data&&(d.data=b.param(d.data,d.traditional)),Ft(Pt,d,n,E),l)return E;for(f in(c=b.event&&d.global)&&0==b.active++&&b.event.trigger(\"ajaxStart\"),d.type=d.type.toUpperCase(),d.hasContent=!Ht.test(d.type),i=d.url.replace(jt,\"\"),d.hasContent?d.data&&d.processData&&0===(d.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&(d.data=d.data.replace(Dt,\"+\")):(p=d.url.slice(i.length),d.data&&(d.processData||\"string\"==typeof d.data)&&(i+=(Ct.test(i)?\"&\":\"?\")+d.data,delete d.data),!1===d.cache&&(i=i.replace(qt,\"$1\"),p=(Ct.test(i)?\"&\":\"?\")+\"_=\"+Tt.guid+++p),d.url=i+p),d.ifModified&&(b.lastModified[i]&&E.setRequestHeader(\"If-Modified-Since\",b.lastModified[i]),b.etag[i]&&E.setRequestHeader(\"If-None-Match\",b.etag[i])),(d.data&&d.hasContent&&!1!==d.contentType||n.contentType)&&E.setRequestHeader(\"Content-Type\",d.contentType),E.setRequestHeader(\"Accept\",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(\"*\"!==d.dataTypes[0]?\", \"+Mt+\"; q=0.01\":\"\"):d.accepts[\"*\"]),d.headers)E.setRequestHeader(f,d.headers[f]);if(d.beforeSend&&(!1===d.beforeSend.call(h,E,d)||l))return E.abort();if(C=\"abort\",m.add(d.complete),E.done(d.success),E.fail(d.error),r=Ft(Rt,d,n,E)){if(E.readyState=1,c&&g.trigger(\"ajaxSend\",[E,d]),l)return E;d.async&&d.timeout>0&&(s=e.setTimeout((function(){E.abort(\"timeout\")}),d.timeout));try{l=!1,r.send(w,S)}catch(e){if(l)throw e;S(-1,e)}}else S(-1,\"No Transport\");function S(t,n,a,u){var f,p,v,w,T,C=n;l||(l=!0,s&&e.clearTimeout(s),r=void 0,o=u||\"\",E.readyState=t>0?4:0,f=t>=200&&t<300||304===t,a&&(w=function(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;\"*\"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader(\"Content-Type\"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+\" \"+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(d,E,a)),!f&&b.inArray(\"script\",d.dataTypes)>-1&&(d.converters[\"text script\"]=function(){}),w=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if(\"*\"===o)o=u;else if(\"*\"!==u&&u!==o){if(!(a=l[u+\" \"+o]||l[\"* \"+o]))for(i in l)if((s=i.split(\" \"))[1]===o&&(a=l[u+\" \"+s[0]]||l[\"* \"+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:\"parsererror\",error:a?e:\"No conversion from \"+u+\" to \"+o}}}return{state:\"success\",data:t}}(d,w,E,f),f?(d.ifModified&&((T=E.getResponseHeader(\"Last-Modified\"))&&(b.lastModified[i]=T),(T=E.getResponseHeader(\"etag\"))&&(b.etag[i]=T)),204===t||\"HEAD\"===d.type?C=\"nocontent\":304===t?C=\"notmodified\":(C=w.state,p=w.data,f=!(v=w.error))):(v=C,!t&&C||(C=\"error\",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+\"\",f?y.resolveWith(h,[p,C,E]):y.rejectWith(h,[E,C,v]),E.statusCode(x),x=void 0,c&&g.trigger(f?\"ajaxSuccess\":\"ajaxError\",[E,d,f?p:v]),m.fireWith(h,[E,C]),c&&(g.trigger(\"ajaxComplete\",[E,d]),--b.active||b.event.trigger(\"ajaxStop\")))}return E},getJSON:function(e,t,n){return b.get(e,t,n,\"json\")},getScript:function(e,t){return b.get(e,void 0,t,\"script\")}}),b.each([\"get\",\"post\"],(function(e,t){b[t]=function(e,n,r,i){return h(n)&&(i=i||r,r=n,n=void 0),b.ajax(b.extend({url:e,type:t,dataType:i,data:n,success:r},b.isPlainObject(e)&&e))}})),b.ajaxPrefilter((function(e){var t;for(t in e.headers)\"content-type\"===t.toLowerCase()&&(e.contentType=e.headers[t]||\"\")})),b._evalUrl=function(e,t,n){return b.ajax({url:e,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,converters:{\"text script\":function(){}},dataFilter:function(e){b.globalEval(e,t,n)}})},b.fn.extend({wrapAll:function(e){var t;return this[0]&&(h(e)&&(e=e.call(this[0])),t=b(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map((function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e})).append(this)),this},wrapInner:function(e){return h(e)?this.each((function(t){b(this).wrapInner(e.call(this,t))})):this.each((function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)}))},wrap:function(e){var t=h(e);return this.each((function(n){b(this).wrapAll(t?e.call(this,n):e)}))},unwrap:function(e){return this.parent(e).not(\"body\").each((function(){b(this).replaceWith(this.childNodes)})),this}}),b.expr.pseudos.hidden=function(e){return!b.expr.pseudos.visible(e)},b.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},b.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var $t={0:200,1223:204},_t=b.ajaxSettings.xhr();d.cors=!!_t&&\"withCredentials\"in _t,d.ajax=_t=!!_t,b.ajaxTransport((function(t){var n,r;if(d.cors||_t&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];for(a in t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i[\"X-Requested-With\"]||(i[\"X-Requested-With\"]=\"XMLHttpRequest\"),i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,\"abort\"===e?s.abort():\"error\"===e?\"number\"!=typeof s.status?o(0,\"error\"):o(s.status,s.statusText):o($t[s.status]||s.status,s.statusText,\"text\"!==(s.responseType||\"text\")||\"string\"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n(\"error\"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout((function(){n&&r()}))},n=n(\"abort\");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}})),b.ajaxPrefilter((function(e){e.crossDomain&&(e.contents.script=!1)})),b.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(e){return b.globalEval(e),e}}}),b.ajaxPrefilter(\"script\",(function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\")})),b.ajaxTransport(\"script\",(function(e){var t,n;if(e.crossDomain||e.scriptAttrs)return{send:function(r,i){t=b(\""
],
"text/plain": [
":Curve [x] (f(x ; μ, σ))"
]
},
"execution_count": 2,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1002"
}
},
"output_type": "execute_result"
}
],
"source": [
"opts = dict(show_grid=True, frame_height=200, frame_width=350, color=\"#1f77b3\")\n",
"\n",
"\n",
"def plot_normal_pdf(mu=0, sigma=1):\n",
" x = np.linspace(-10, 10, 200)\n",
" y = scipy.stats.norm.pdf(x, loc=mu, scale=sigma)\n",
"\n",
" return hv.Curve(data=(x, y), kdims=[\"x\"], vdims=[\"f(x ; μ, σ)\"]).opts(\n",
" **opts\n",
" )\n",
"\n",
"\n",
"plot_normal_pdf(0, 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good, but what if we want to examine how the PDF changes with μ and σ? We could keep plotting it over and over, manually changing the values of µ and σ. Much more instructive would be to create sliders where we can change the values of the parameters and instantaneously see how the plot changes. We can use Panel to create interactive sliders using the FloatSlider widget. To code below implements a simple dashboard, and I comment on the syntax immediately after."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Row\n",
" [0] ParamFunction(function)\n",
" [1] Spacer(width=15)\n",
" [2] Column(width=200)\n",
" [0] Spacer(height=30)\n",
" [1] FloatSlider(end=5, name='µ', start=-5)\n",
" [2] Spacer(height=15)\n",
" [3] FloatSlider(end=5, name='σ', start=0.1, value=1)"
]
},
"execution_count": 3,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1107"
}
},
"output_type": "execute_result"
}
],
"source": [
"mu_slider = pn.widgets.FloatSlider(\n",
" name=\"µ\", start=-5, end=5, step=0.1, value=0\n",
")\n",
"sigma_slider = pn.widgets.FloatSlider(\n",
" name=\"σ\", start=0.1, end=5, step=0.1, value=1\n",
")\n",
"\n",
"\n",
"@pn.depends(mu_slider.param.value, sigma_slider.param.value)\n",
"def plot_normal_pdf(mu=0, sigma=1):\n",
" x = np.linspace(-10, 10, 200)\n",
" y = scipy.stats.norm.pdf(x, loc=mu, scale=sigma)\n",
"\n",
" return hv.Curve(data=(x, y), kdims=[\"x\"], vdims=[\"f(x ; μ, σ)\"]).opts(\n",
" **opts\n",
" )\n",
"\n",
"\n",
"widgets = pn.Column(\n",
" pn.Spacer(height=30),\n",
" mu_slider,\n",
" pn.Spacer(height=15),\n",
" sigma_slider,\n",
" width=200,\n",
")\n",
"pn.Row(plot_normal_pdf, pn.Spacer(width=15), widgets)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's go through each component. First, we define our widgets, `mu_slider` and `sigma_slider`. When building more complicated dashboards, we can look at the [Panel documentation](https://panel.pyviz.org/reference/index.html) to choose which widgets we want to use. \n",
"\n",
"Next, we define our plotting function, `plot_normal_pdf()`. Here we use Holoviews, but we could use Bokeh (or even Matplotlib or Altair). Notice the `@pn.depends` function **decorator**. This links the input from the widget to the computation in the function, so every time we change the interactive widget, the output of the function updates. (We will not discuss decorators in the bootcamp. For this dashboarding application is suffices to know that using the `@pn.depends` decorator links up the parameter values in the input of the function to the values of the sliders.)\n",
"\n",
"Finally, we set the layout of our dashboard. We can define rows and columns through `pn.Row` and `pn.Column` respectively. We can set their heights and widths and add spaces through `pn.Spacer`. You may have to play around a bit to get it in the format that looks best to you."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Using Panel to explore parameters\n",
"\n",
"Recall from [Exercise 7.4](exercise_7.4.ipynb) that we investigated the fold change in gene expression as a function of repressor copy number $R$ and inducer concentration $c$. The theoretical function, based on an MWC model, was\n",
"\n",
"\\begin{align}\n",
"\\text{fold change} = \\left[1 + \\frac{\\frac{R}{K}\\left(1 + c/K_\\mathrm{d}^\\mathrm{A}\\right)^2}{\\left(1 + c/K_\\mathrm{d}^\\mathrm{A}\\right)^2 + K_\\mathrm{switch}\\left(1 + c/K_\\mathrm{d}^\\mathrm{I}\\right)^2}\\right]^{-1}.\n",
"\\end{align}\n",
"\n",
"There are quite a few parameters here.\n",
"\n",
"|Parameter|Description|\n",
"|:--:|:--:|\n",
"|$K_\\mathrm{d}^\\mathrm{A}$|dissoc. const. for active repressor binding IPTG|\n",
"|$K_\\mathrm{d}^\\mathrm{I}$|dissoc. const. for inactive repressor binding IPTG|\n",
"|$K_\\mathrm{switch}$|equil. const. for switching active/inactive|\n",
"|$K$|dissoc. const. for active repressor binding operator|\n",
"|$R$|number of repressors in cell|\n",
"\n",
"This is a complicated function of these parameters, and we might want to see how the fold change vs. inducer concentration curve varies based on various parameter values. Dashboarding comes in very handy for this kind of application.\n",
"\n",
"To build our dashboard, we start by defining functions to compute the fold change as a function of the IPTG concentration and the parameters."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def bohr_parameter(c, R, K, KdA, KdI, Kswitch):\n",
" \"\"\"Compute Bohr parameter based on MWC model.\"\"\"\n",
" # Big nasty argument of logarithm\n",
" log_arg = (1 + c / KdA) ** 2 / (\n",
" (1 + c / KdA) ** 2 + Kswitch * (1 + c / KdI) ** 2\n",
" )\n",
"\n",
" return -np.log(R / K) - np.log(log_arg)\n",
"\n",
"\n",
"def fold_change(c, R, K, KdA, KdI, Kswitch):\n",
" \"\"\"Compute theoretical fold change for MWC model.\"\"\"\n",
" return 1 / (1 + np.exp(-bohr_parameter(c, R, K, KdA, KdI, Kswitch)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we define our sliders. As we explore this function, we would like the parameter to vary on a logarithmic scale. Panel currently does not allow for logarithmic scale on sliders, so we have to specify the parameters as being the logarithm of the parameters."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"log_R_slider = pn.widgets.FloatSlider(\n",
" name=\"log₁₀ R (1/cell)\", start=0, end=3, step=0.1, value=2\n",
")\n",
"log_K_slider = pn.widgets.FloatSlider(\n",
" name=\"log₁₀ K (1/cell)\", start=-6, end=3, step=0.1, value=0\n",
")\n",
"log_KdA_slider = pn.widgets.FloatSlider(\n",
" name=\"log₁₀ KdA (1/mM)\", start=-6, end=3, step=0.1, value=-2\n",
")\n",
"log_KdI_slider = pn.widgets.FloatSlider(\n",
" name=\"log₁₀ KdI (1/mM)\", start=-6, end=3, step=0.1, value=-2\n",
")\n",
"log_Kswitch_slider = pn.widgets.FloatSlider(\n",
" name=\"log₁₀ Kswitch\", start=-3, end=6, step=0.1, value=1,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now write a function to generate a plot, given the parameters. We have to use the `@pn.depends()` decorator to "
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"@pn.depends(\n",
" log_R_slider.param.value,\n",
" log_K_slider.param.value,\n",
" log_KdA_slider.param.value,\n",
" log_KdI_slider.param.value,\n",
" log_Kswitch_slider.param.value,\n",
")\n",
"def plot_curve(log_R, log_K, log_KdA, log_KdI, log_Kswitch):\n",
" params = 10.0 ** np.array([log_R, log_K, log_KdA, log_KdI, log_Kswitch])\n",
" c = np.logspace(-6, 2, 200)\n",
"\n",
" opts = dict(\n",
" frame_height=250,\n",
" frame_width=350,\n",
" logx=True,\n",
" show_grid=True,\n",
" xlabel=\"[IPTG] (mM)\",\n",
" ylabel=\"fold change\",\n",
" ylim=(-0.05, 1.05),\n",
" color=\"#1f77b3\",\n",
" )\n",
"\n",
" return hv.Curve((c, fold_change(c, *params))).opts(**opts)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we can lay out our dashboard and explore the function."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Row\n",
" [0] ParamFunction(function)\n",
" [1] Spacer(width=15)\n",
" [2] Column(width=200)\n",
" [0] FloatSlider(end=3, name='log₁₀ R (1/cell)', value=2)\n",
" [1] FloatSlider(end=3, name='log₁₀ K (1/cell)', start=-6)\n",
" [2] FloatSlider(end=3, name='log₁₀ KdA (1/mM)', start=-6, value=-2)\n",
" [3] FloatSlider(end=3, name='log₁₀ KdI (1/mM)', start=-6, value=-2)\n",
" [4] FloatSlider(end=6, name='log₁₀ Kswitch', start=-3, value=1)"
]
},
"execution_count": 7,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1241"
}
},
"output_type": "execute_result"
}
],
"source": [
"pn.Row(\n",
" plot_curve,\n",
" pn.Spacer(width=15),\n",
" pn.Column(\n",
" log_R_slider,\n",
" log_K_slider,\n",
" log_KdA_slider,\n",
" log_KdI_slider,\n",
" log_Kswitch_slider,\n",
" width=200,\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In playing with the slider, we see that a difference between $K_\\mathrm{d}^\\mathrm{A}$ and $K_\\mathrm{d}^\\mathrm{I}$ is required to get repression. As we would expect, we need $K_\\mathrm{d}^\\mathrm{I} < K_\\mathrm{d}^\\mathrm{A}$ in order to get more repression with increasing IPTG concentration.\n",
"\n",
"The effects of the other parameters are more complicated and interdependent, but can nonetheless be explored by varying the sliders."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bacterial growth\n",
"\n",
"In auxiliary lessons, we will explore image processing. Here, we will make a dashboard to display images in a time lapse. We will display a time lapse movie of growing _Bacillus subtilis_ cells, acquired by Jin Park from the Elowitz lab. The image are stored in files named like `data/bacterial_growth/bacillus_001.tif`, for a total of 55 frames. To load an image, we use `skimage.io.imread()`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"im = skimage.io.imread('data/bacterial_growth/bacillus_001.tif')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This stores the image as a Numpy array. To display the image, we can use HoloView's Image Element. Before using that, we need to set up some of the image's dimensions first. To get the scale of the axes right, we need to know the interpixel distance. From the metadata provided by Jin Park, the interpixel distance is 64.5 nanometers."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"ip_distance = 0.0645"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since we're doing a time lapse, we should also know the time between frames. In this case, it was 15 minutes."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"dt = 15"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To get the aspect ratio correct, we need to specify the frame width we want, and then set the height accordingly."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"frame_width = 200\n",
"frame_height = int(frame_width * im.shape[0] / im.shape[1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now set the `bounds` kwarg for our call to `hv.Image()`."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"bounds = [0, 0, im.shape[1]*ip_distance, im.shape[0]*ip_distance]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we're ready to plot the image."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
":Image [x,y] (z)"
]
},
"execution_count": 13,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1376"
}
},
"output_type": "execute_result"
}
],
"source": [
"hv.Image(im, bounds=bounds).opts(\n",
" xlabel=\"µm\",\n",
" ylabel=\"µm\",\n",
" title=\"t = 0 min\",\n",
" frame_width=frame_width,\n",
" frame_height=frame_height,\n",
" cmap='viridis',\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Be default, we are displaying the image with a **Viridis colormap**, which goes from purple for low intensity to yellow for high. This default was set when we called `bootcamp_utils.hv_defaults.set_defaults()`, and is a good perceptual default colormap.\n",
"\n",
"Now let's build our dashboard. We want a slider to switch from frame to frame and also a pulldown menu that allows us to switch colormaps. Because frame numbers are integers, we use an `IntSlider` instead of a `FloatSlider`. For the color map, we use a `Select` widget."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Row\n",
" [0] ParamFunction(function)\n",
" [1] Spacer(width=15)\n",
" [2] Column\n",
" [0] Spacer(height=30)\n",
" [1] IntSlider(end=55, name='frame', start=1, value=1)\n",
" [2] Spacer(height=15)\n",
" [3] Select(name='colormap', options=['gray', 'fire', ...], value='viridis')"
]
},
"execution_count": 14,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1479"
}
},
"output_type": "execute_result"
}
],
"source": [
"frame_slider = pn.widgets.IntSlider(name=\"frame\", start=1, end=55, value=1)\n",
"colormap_selector = pn.widgets.Select(\n",
" name=\"colormap\",\n",
" options=[\"gray\", \"fire\", \"magma\", \"viridis\"],\n",
" value=\"viridis\",\n",
")\n",
"\n",
"\n",
"@pn.depends(frame_slider.param.value, colormap_selector.param.value)\n",
"def show_bacillus(frame, cmap):\n",
" # Load in appropriate image\n",
" fname = \"data/bacterial_growth/bacillus_{frame:03d}.tif\".format(\n",
" frame=frame\n",
" )\n",
" im = skimage.io.imread(fname)\n",
"\n",
" return hv.Image(im, bounds=bounds).opts(\n",
" xlabel=\"µm\",\n",
" ylabel=\"µm\",\n",
" title=f\"t = {dt*(frame-1)} min\",\n",
" frame_width=frame_width,\n",
" frame_height=frame_height,\n",
" cmap=cmap,\n",
" )\n",
"\n",
"\n",
"pn.Row(\n",
" show_bacillus,\n",
" pn.Spacer(width=15),\n",
" pn.Column(\n",
" pn.Spacer(height=30),\n",
" frame_slider,\n",
" pn.Spacer(height=15),\n",
" colormap_selector,\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exploring a data set\n",
"\n",
"As an example of dashboarding put to use to explore a data set, we turn again to the data set from [Beattie, et al.](https://doi.org/10.1098/rsos.160321) studying how sleep deprivation affects facial matching ability. Let's load in the data set and take a look to remind ourselves of the variables."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" participant number | \n",
" gender | \n",
" age | \n",
" correct hit percentage | \n",
" correct reject percentage | \n",
" percent correct | \n",
" confidence when correct hit | \n",
" confidence when incorrect hit | \n",
" confidence when correct reject | \n",
" confidence when incorrect reject | \n",
" confidence when correct | \n",
" confidence when incorrect | \n",
" sci | \n",
" psqi | \n",
" ess | \n",
" insomnia | \n",
"
\n",
" \n",
" \n",
" \n",
" 0 | \n",
" 8 | \n",
" f | \n",
" 39 | \n",
" 65 | \n",
" 80 | \n",
" 72.5 | \n",
" 91.0 | \n",
" 90.0 | \n",
" 93.0 | \n",
" 83.5 | \n",
" 93.0 | \n",
" 90.0 | \n",
" 9 | \n",
" 13 | \n",
" 2 | \n",
" True | \n",
"
\n",
" \n",
" 1 | \n",
" 16 | \n",
" m | \n",
" 42 | \n",
" 90 | \n",
" 90 | \n",
" 90.0 | \n",
" 75.5 | \n",
" 55.5 | \n",
" 70.5 | \n",
" 50.0 | \n",
" 75.0 | \n",
" 50.0 | \n",
" 4 | \n",
" 11 | \n",
" 7 | \n",
" True | \n",
"
\n",
" \n",
" 2 | \n",
" 18 | \n",
" f | \n",
" 31 | \n",
" 90 | \n",
" 95 | \n",
" 92.5 | \n",
" 89.5 | \n",
" 90.0 | \n",
" 86.0 | \n",
" 81.0 | \n",
" 89.0 | \n",
" 88.0 | \n",
" 10 | \n",
" 9 | \n",
" 3 | \n",
" True | \n",
"
\n",
" \n",
" 3 | \n",
" 22 | \n",
" f | \n",
" 35 | \n",
" 100 | \n",
" 75 | \n",
" 87.5 | \n",
" 89.5 | \n",
" NaN | \n",
" 71.0 | \n",
" 80.0 | \n",
" 88.0 | \n",
" 80.0 | \n",
" 13 | \n",
" 8 | \n",
" 20 | \n",
" True | \n",
"
\n",
" \n",
" 4 | \n",
" 27 | \n",
" f | \n",
" 74 | \n",
" 60 | \n",
" 65 | \n",
" 62.5 | \n",
" 68.5 | \n",
" 49.0 | \n",
" 61.0 | \n",
" 49.0 | \n",
" 65.0 | \n",
" 49.0 | \n",
" 13 | \n",
" 9 | \n",
" 12 | \n",
" True | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" participant number gender age correct hit percentage \\\n",
"0 8 f 39 65 \n",
"1 16 m 42 90 \n",
"2 18 f 31 90 \n",
"3 22 f 35 100 \n",
"4 27 f 74 60 \n",
"\n",
" correct reject percentage percent correct confidence when correct hit \\\n",
"0 80 72.5 91.0 \n",
"1 90 90.0 75.5 \n",
"2 95 92.5 89.5 \n",
"3 75 87.5 89.5 \n",
"4 65 62.5 68.5 \n",
"\n",
" confidence when incorrect hit confidence when correct reject \\\n",
"0 90.0 93.0 \n",
"1 55.5 70.5 \n",
"2 90.0 86.0 \n",
"3 NaN 71.0 \n",
"4 49.0 61.0 \n",
"\n",
" confidence when incorrect reject confidence when correct \\\n",
"0 83.5 93.0 \n",
"1 50.0 75.0 \n",
"2 81.0 89.0 \n",
"3 80.0 88.0 \n",
"4 49.0 65.0 \n",
"\n",
" confidence when incorrect sci psqi ess insomnia \n",
"0 90.0 9 13 2 True \n",
"1 50.0 4 11 7 True \n",
"2 88.0 10 9 3 True \n",
"3 80.0 13 8 20 True \n",
"4 49.0 13 9 12 True "
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.read_csv('data/gfmt_sleep.csv', na_values='*')\n",
"\n",
"# Add column for insomnia\n",
"df['insomnia'] = df['sci'] <= 16\n",
"\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The metadata for each subject is the participant number, gender, age, sleep indicators (SCI, PSQI, and ESS), and the column we addded to specify if the subject suffers from insomnia. The measurements for each subject are the various percentages. \n",
"\n",
"Because the data is high-dimensional, it is difficult to visualize all of the data at once. One option is to make a **gridmatrix** where each pair of variables are plotted. This is possibly using HoloViews's `gridmatrix` operation (see [here](http://holoviews.org/gallery/demos/bokeh/iris_splom_example.html#demos-bokeh-gallery-iris-splom-example) for an example). There are many different dimensions we could plot, but the grid of plots will grow too big for the screen, so we will start with just plotting three dimensions, the percent correct, confidence when correct, and confidence when incorrect."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"dims = [\n",
" \"percent correct\",\n",
" \"confidence when correct\",\n",
" \"confidence when incorrect\",\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we'll set up the styling options for our plots in the gridmatrix."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"opts = dict(\n",
" frame_height=150,\n",
" frame_width=150,\n",
" show_grid=True,\n",
" color=hv.Cycle(colorcet.b_glasbey_category10),\n",
" tools=[\"lasso_select\", \"box_select\"],\n",
" size=2,\n",
")\n",
"\n",
"points_opts = hv.opts.Points(**opts)\n",
"scatter_opts = hv.opts.Scatter(**opts)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, to make the plot, we convert the `DataFrame` to a HoloViews `Dataset` instance. Once we do that, we can use the `gridmatrix()` operation to make the plot."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
":GridMatrix [X,Y]\n",
" :Scatter [percent correct] (percent correct)"
]
},
"execution_count": 18,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1611"
}
},
"output_type": "execute_result"
}
],
"source": [
"ds = hv.Dataset(df[dims])\n",
"\n",
"hv.operation.gridmatrix(ds, chart_type=hv.Points, diagonal_type=hv.Scatter).opts(\n",
" points_opts, scatter_opts\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note what if you use the lasso or box select tool, you get **linked brushing**. The point you select on one plot are highlighted in all others. This is quite useful for exploring complex data sets.\n",
"\n",
"To build a dashboard for this data set, we would like to select which dimensions we want to include in the gridmatrix. We can do that with a [checkbox group](https://panel.holoviz.org/reference/widgets/CheckBoxGroup.html)."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"dims_selector = pn.widgets.CheckBoxGroup(\n",
" name=\"dimensions\",\n",
" value=[\"percent correct\", \"confidence when correct\", \"confidence when incorrect\",],\n",
" options=[\n",
" \"participant number\",\n",
" \"age\",\n",
" \"correct hit percentage\",\n",
" \"correct reject percentage\",\n",
" \"percent correct\",\n",
" \"confidence when correct hit\",\n",
" \"confidence when incorrect hit\",\n",
" \"confidence when correct reject\",\n",
" \"confidence when incorrect reject\",\n",
" \"confidence when correct\",\n",
" \"confidence when incorrect\",\n",
" \"sci\",\n",
" \"psqi\",\n",
" \"ess\",\n",
" ],\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We may also want to color the points according to a categorical variable, like gender or insomnia state. We can have a dropdown menu for that."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"colorby_selector = pn.widgets.Select(\n",
" name=\"color by\",\n",
" options=[\"none\", \"gender\", \"insomnia\",],\n",
" value=\"none\",\n",
" width=150,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we write a function to make the gridmatrix. If we want to color by a category, we need to perform a `groupby` operation on the HoloViews `Dataset`; otherwise the syntax is the same."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"@pn.depends(dims_selector.param.value, colorby_selector.param.value)\n",
"def gridmatrix(dims, colorby):\n",
" if colorby == \"none\":\n",
" ds = hv.Dataset(df[dims])\n",
" else:\n",
" ds = hv.Dataset(df[dims + [colorby]]).groupby(\n",
" colorby, container_type=hv.NdOverlay\n",
" )\n",
"\n",
" return hv.operation.gridmatrix(\n",
" ds, chart_type=hv.Points, diagonal_type=hv.Scatter\n",
" ).opts(points_opts, scatter_opts)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we're ready to lay out the dashboard."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Row\n",
" [0] ParamFunction(function)\n",
" [1] Spacer(width=15)\n",
" [2] Column\n",
" [0] Spacer(height=15)\n",
" [1] CheckBoxGroup(name='dimensions', options=['participant number', ...], value=['percent correct', ...])\n",
" [2] Spacer(height=15)\n",
" [3] Select(name='color by', options=['none', 'gender', ...], value='none', width=150)"
]
},
"execution_count": 22,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "3312"
}
},
"output_type": "execute_result"
}
],
"source": [
"pn.Row(\n",
" gridmatrix,\n",
" pn.Spacer(width=15),\n",
" pn.Column(\n",
" pn.Spacer(height=15), dims_selector, pn.Spacer(height=15), colorby_selector\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### More fine-grained control of appearance\n",
"\n",
"This is a nice dashboard, satisfactory for most exploration, I'd say. But there are some problems with the display due to how HoloViews handles overlays. When we color by a categorical variable, linked brushing no longer works (this is due to the way HoloViews handles data sources). Furthermore, we can sometimes get alignment problems on the left most column of the gridmatrix (also a HoloViews issue).\n",
"\n",
"For these reasons, we can get a more effective dashboard if we write our own gridmatrix function using Bokeh and use it instead. As you will see, it takes a bit more effort to get the extra customizability. This highlights the difference between high- and low-level plotting. \n",
"\n",
"In order to have good linked brushing, we need to have a shared **data source** between them. Furthermore, we will need to build the gridmatrix using Bokeh so that we can have them all linked. So, let's start by making the data source that Bokeh can use for all plots. We convert the data frame to a `ColumnDataSource`."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"source = bokeh.models.ColumnDataSource(df)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we write a function to make the gridmatrix using Bokeh. First, we'll make a dictionary of abbreviated axis labels to make it look nicer."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"abbrev = {\n",
" \"participant number\": \"part num\",\n",
" \"age\": \"age\",\n",
" \"correct hit percentage\": \"corr hit %\",\n",
" \"correct reject percentage\": \"corr rej %\",\n",
" \"percent correct\": \"% corr\",\n",
" \"confidence when correct hit\": \"conf corr hit\",\n",
" \"confidence when incorrect hit\": \"conf incorr hit\",\n",
" \"confidence when correct reject\": \"cont corr rej\",\n",
" \"confidence when incorrect reject\": \"conf incorr rej\",\n",
" \"confidence when correct\": \"conf corr\",\n",
" \"confidence when incorrect\": \"conf incorr\",\n",
" \"sci\": \"sci\",\n",
" \"psqi\": \"psqi\",\n",
" \"ess\": \"ess\",\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we'll write the function to make the gridmatrix. I will not go over the details of the function. This part of the lesson is simply to demonstrate you that you have increased control of the appearances of your plots and dashboards if you are willing to do some hacking and use lower-level plotting libraries. Note now many more links this function is than when we used HoloViews above. Nonetheless, it is not too terrible to code this up.\n",
"\n",
"Before we do that, we will make one more selector widget. When we do linked brushing, we can decide whether or not we want the nonselected points to be more transparent or to be completely invisible. For this particular data set, making them more transparent can be a bit confusing because when multiple points lay on top of each other, the resulting data point may appear as dark as a single selected data point."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"alpha_selector = pn.widgets.Select(\n",
" name=\"nonselected\", options=[\"invisible\", \"more transparent\"]\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can proceed to make our own gridmatrix function using Bokeh."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"@pn.depends(\n",
" dims_selector.param.value,\n",
" colorby_selector.param.value,\n",
" alpha_selector.param.value,\n",
")\n",
"def gridmatrix(dims, colorby, alpha):\n",
" # Set up list of list of plots\n",
" plots = [[None for _ in dims] for _ in dims]\n",
"\n",
" # Set up coloring\n",
" if colorby == \"none\":\n",
" color = colorcet.b_glasbey_category10[0]\n",
" else:\n",
" source.data[\"colorby\"] = source.data[colorby].astype(str)\n",
" color = bokeh.transform.factor_cmap(\n",
" \"colorby\",\n",
" palette=colorcet.b_glasbey_category10,\n",
" factors=sorted(np.unique(source.data[\"colorby\"])),\n",
" )\n",
"\n",
" nonselection_alpha = 0 if alpha == \"invisible\" else 0.1\n",
"\n",
" tools = \"pan,box_zoom,wheel_zoom,lasso_select,box_select,reset,save\"\n",
"\n",
" # Build diagonal scatter plot (have to do first to get linking to work properly)\n",
" for i, x in enumerate(dims):\n",
" x_axis_label = abbrev[x] if i == len(dims) - 1 else None\n",
" y_axis_label = abbrev[x] if i == 0 else None\n",
"\n",
" # Manually set data range for better linking of ranges\n",
" source_data_range = (\n",
" np.nanmin(source.data[x]),\n",
" np.nanmax(source.data[x]),\n",
" )\n",
" dist = source_data_range[1] - source_data_range[0]\n",
" x_range = [\n",
" source_data_range[0] - 0.05 * dist,\n",
" source_data_range[1] + 0.05 * dist,\n",
" ]\n",
"\n",
" plots[i][i] = bokeh.plotting.figure(\n",
" frame_height=125,\n",
" frame_width=125,\n",
" x_axis_label=x_axis_label,\n",
" y_axis_label=y_axis_label,\n",
" tools=tools,\n",
" align=\"end\",\n",
" x_range=x_range,\n",
" )\n",
" plots[i][i].circle(\n",
" source=source,\n",
" x=x,\n",
" y=x,\n",
" alpha=0.7,\n",
" size=2,\n",
" color=color,\n",
" nonselection_alpha=nonselection_alpha,\n",
" )\n",
" plots[i][i].y_range = plots[i][i].x_range\n",
"\n",
" # Build each scatter plot\n",
" for j, x in enumerate(dims):\n",
" for i, y in enumerate(dims):\n",
" if i != j:\n",
" x_axis_label = abbrev[x] if i == len(dims) - 1 else None\n",
" y_axis_label = abbrev[y] if j == 0 else None\n",
"\n",
" plots[i][j] = bokeh.plotting.figure(\n",
" frame_height=125,\n",
" frame_width=125,\n",
" x_axis_label=x_axis_label,\n",
" y_axis_label=y_axis_label,\n",
" tools=tools,\n",
" align=\"end\",\n",
" x_range=plots[j][j].x_range,\n",
" y_range=plots[i][i].x_range,\n",
" )\n",
" plots[i][j].circle(\n",
" source=source,\n",
" x=x,\n",
" y=y,\n",
" alpha=0.7,\n",
" size=2,\n",
" color=color,\n",
" nonselection_alpha=nonselection_alpha,\n",
" )\n",
"\n",
" # Only show tick labels on edges\n",
" for i in range(len(dims) - 1):\n",
" for j in range(1, len(dims)):\n",
" plots[i][j].axis.visible = False\n",
" for j in range(1, len(dims)):\n",
" plots[-1][j].yaxis.visible = False\n",
" for i in range(0, len(dims) - 1):\n",
" plots[i][0].xaxis.visible = False\n",
"\n",
" return bokeh.layouts.gridplot(plots)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's re-do the layout. The responsiveness will be a bit slow because every time we change a checkbox or the color by field, the HoloViews dashboard above also gets updated. For a more performant dashboard, re-run the notebook, but do not invoke the HoloViews-based dashboard."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Row\n",
" [0] ParamFunction(function)\n",
" [1] Spacer(width=15)\n",
" [2] Column\n",
" [0] Spacer(height=15)\n",
" [1] CheckBoxGroup(name='dimensions', options=['participant number', ...], value=['percent correct', ...])\n",
" [2] Spacer(height=15)\n",
" [3] Select(name='color by', options=['none', 'gender', ...], value='none', width=150)\n",
" [4] Spacer(height=15)\n",
" [5] Select(name='nonselected', options=['invisible', ...], value='invisible')"
]
},
"execution_count": 27,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "5701"
}
},
"output_type": "execute_result"
}
],
"source": [
"pn.Row(\n",
" gridmatrix,\n",
" pn.Spacer(width=15),\n",
" pn.Column(\n",
" pn.Spacer(height=15),\n",
" dims_selector,\n",
" pn.Spacer(height=15),\n",
" colorby_selector,\n",
" pn.Spacer(height=15),\n",
" alpha_selector,\n",
" ),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Putting it all together\n",
"\n",
"The scatter plots are useful, but we would like to have a clear comparison of individual variables across insomnia conditions and across gender. We can therefore add plots of the ECDFs below the gridmatrix. We will add one more checkbox, enabling us to select whether or not we want confidence intervals on the ECDF."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [],
"source": [
"conf_int_selector = pn.widgets.Checkbox(\n",
" name=\"ECDF confidence interval\", value=True\n",
")\n",
"\n",
"\n",
"@pn.depends(\n",
" dims_selector.param.value,\n",
" colorby_selector.param.value,\n",
" conf_int_selector.param.value,\n",
")\n",
"def ecdfs(dims, cat, conf_int):\n",
" if cat == \"gender\":\n",
" order = [\"f\", \"m\"]\n",
" elif cat == \"insomnia\":\n",
" order = [False, True]\n",
" elif cat == \"none\":\n",
" cat = None\n",
" order = None\n",
"\n",
" plots = []\n",
"\n",
" for i, dim in enumerate(dims):\n",
" plots.append(\n",
" bokeh_catplot.ecdf(\n",
" df,\n",
" cat,\n",
" dim,\n",
" frame_height=150,\n",
" frame_width=250,\n",
" show_legend=(i == len(dims) - 1),\n",
" order=order,\n",
" style=\"staircase\",\n",
" conf_int=conf_int,\n",
" )\n",
" )\n",
"\n",
" return bokeh.layouts.gridplot(plots, ncols=2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can construct the final layout of the dashboard. We will place the check boxes and selectors on top, followed by the ECDFs, and finally the grid matrix."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Column\n",
" [0] Row\n",
" [0] CheckBoxGroup(name='dimensions', options=['participant number', ...], value=['percent correct', ...])\n",
" [1] Spacer(width=15)\n",
" [2] Column\n",
" [0] Select(name='color by', options=['none', 'gender', ...], value='none', width=150)\n",
" [1] Spacer(height=15)\n",
" [2] Select(name='nonselected', options=['invisible', ...], value='invisible')\n",
" [3] Spacer(height=15)\n",
" [4] Checkbox(name='ECDF confidence i..., value=True)\n",
" [1] Spacer(height=15)\n",
" [2] ParamFunction(function)\n",
" [3] Spacer(height=15)\n",
" [4] ParamFunction(function)"
]
},
"execution_count": 29,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "7357"
}
},
"output_type": "execute_result"
}
],
"source": [
"pn.Column(\n",
" pn.Row(\n",
" dims_selector,\n",
" pn.Spacer(width=15),\n",
" pn.Column(\n",
" colorby_selector,\n",
" pn.Spacer(height=15),\n",
" alpha_selector,\n",
" pn.Spacer(height=15),\n",
" conf_int_selector,\n",
" ),\n",
" ),\n",
" pn.Spacer(height=15),\n",
" ecdfs,\n",
" pn.Spacer(height=15),\n",
" gridmatrix,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusions\n",
"\n",
"There are many more directions you can go with dashboards. In particular, if there is a type of experiment you do often in which you have multifaceted data, you may want to build a dashboard into which you can automatically load your data and display it for you to explore. This can greatly expedite your work, and can also be useful for sharing your data with others, enabling them to rapidly explore it as well.\n",
"\n",
"That said, it is important to constantly be rethinking how you visualize and analyze the data you collect. You do not want the displays of a dashboard you set up a year ago have undo influence on your thinking right now."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Computing environment"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPython 3.7.7\n",
"IPython 7.15.0\n",
"\n",
"numpy 1.18.1\n",
"scipy 1.4.1\n",
"pandas 0.24.2\n",
"skimage 0.16.2\n",
"bootcamp_utils 0.0.6\n",
"bokeh 2.1.0\n",
"holoview not installed\n",
"panel 0.9.6\n",
"colorcet 2.0.2\n",
"jupyterlab 2.1.4\n"
]
}
],
"source": [
"%load_ext watermark\n",
"%watermark -v -p numpy,scipy,pandas,skimage,bootcamp_utils,bokeh,holoview,panel,colorcet,jupyterlab"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.7.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}