diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0d3101985 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.sass-cache/ diff --git a/Gemfile b/Gemfile index dfbd3b4a0..3df2b31ec 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,11 @@ +source "https://rubygems.org" + +gem "jekyll", "~> 3.9.0" +gem "webrick" +gem "kramdown-parser-gfm" + group :jekyll_plugins do gem "jekyll-remote-theme" gem "jekyll-paginate" gem "jekyll-sitemap" - gem "github-pages" -end +end \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..9e5fc94e5 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,117 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + colorator (1.1.0) + concurrent-ruby (1.3.8) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.4-x86_64-linux-gnu) + forwardable-extended (2.6.0) + http_parser.rb (0.8.1) + i18n (1.15.2) + concurrent-ruby (~> 1.0) + jekyll (3.9.5) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (>= 0.7, < 2) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (>= 1.17, < 3) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-paginate (1.1.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.5.2) + rexml (>= 3.4.4) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + mercenary (0.3.6) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (7.0.5) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.4.4) + rouge (3.30.0) + rubyzip (2.4.1) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + webrick (1.9.2) + +PLATFORMS + x86_64-linux-gnu + +DEPENDENCIES + jekyll (~> 3.9.0) + jekyll-paginate + jekyll-remote-theme + jekyll-sitemap + kramdown-parser-gfm + webrick + +CHECKSUMS + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af + bundler (4.0.17) sha256=214e21431b5665dd2f99df8a5511c6b151d7a72e8015c8b38f8b775b61cbb6c1 + colorator (1.1.0) sha256=e2f85daf57af47d740db2a32191d1bdfb0f6503a0dfbc8327d0c9154d5ddfc38 + concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1 + em-websocket (0.5.3) sha256=f56a92bde4e6cb879256d58ee31f124181f68f8887bd14d53d5d9a292758c6a8 + eventmachine (1.2.7) sha256=994016e42aa041477ba9cff45cbe50de2047f25dd418eba003e84f0d16560972 + ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d + forwardable-extended (2.6.0) sha256=1bec948c469bbddfadeb3bd90eb8c85f6e627a412a3e852acfd7eaedbac3ec97 + http_parser.rb (0.8.1) sha256=9ae8df145b39aa5398b2f90090d651c67bd8e2ebfe4507c966579f641e11097a + i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 + jekyll (3.9.5) sha256=c22a95f18036c49f6549afdc28787c4d99fc6f26a1af3e867578c918ec9a66f0 + jekyll-paginate (1.1.0) sha256=880aadf4b02529a93541d508c5cbb744f014cbfc071d0263a31f25ec9066eb64 + jekyll-remote-theme (0.4.3) sha256=d3fde726484fb3df04de9e347baf75aaa3d5bfea771a330412e0c52608e54b40 + jekyll-sass-converter (1.5.2) sha256=53773669e414dc3bb070113befacb808576025a28cfa4a4accc682e90a9c1101 + jekyll-sitemap (1.4.0) sha256=0de08c5debc185ea5a8f980e1025c7cd3f8e0c35c8b6ef592f15c46235cf4218 + jekyll-watch (2.2.1) sha256=bc44ed43f5e0a552836245a54dbff3ea7421ecc2856707e8a1ee203a8387a7e1 + kramdown (2.5.2) sha256=1ba542204c66b6f9111ff00dcc26075b95b220b07f2905d8261740c82f7f02fa + kramdown-parser-gfm (1.1.0) sha256=fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729 + liquid (4.0.4) sha256=4fcfebb1a045e47918388dbb7a0925e7c3893e58d2bd6c3b3c73ec17a2d8fdb3 + listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mercenary (0.3.6) sha256=2a084b18f5692c86a633e185d5311ba6d11fc46c802eb414ae05368178078a82 + pathutil (0.16.2) sha256=e43b74365631cab4f6d5e4228f812927efc9cb2c71e62976edcb252ee948d589 + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 + rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe + rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.30.0) sha256=a3d353222aa72e49e2c86726c0bcfd719f82592f57d494474655f48e669eceb6 + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + safe_yaml (1.0.5) sha256=a6ac2d64b7eb027bdeeca1851fe7e7af0d668e133e8a88066a0c6f7087d9f848 + sass (3.7.4) sha256=808b0d39053aa69068df939e24671fe84fd5a9d3314486e1a1457d0934a4255d + sass-listen (4.0.0) sha256=ae9dcb76dd3e234329e5ba6e213f48e532c5a3e7b0b4d8a87f13aaca0cc18377 + webrick (1.9.2) sha256=beb4a15fc474defed24a3bda4ffd88a490d517c9e4e6118c3edce59e45864131 + +BUNDLED WITH + 4.0.17 diff --git a/README.md b/README.md index 2511630f3..b882b22e4 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# my page +# my page! diff --git a/README_2_run_local_instance.txt b/README_2_run_local_instance.txt new file mode 100644 index 000000000..afe7aa0a4 --- /dev/null +++ b/README_2_run_local_instance.txt @@ -0,0 +1,8 @@ +To run these git-pages locally, use the following commands from the terminal in the directory of the Gemfile + +Note: There is a dependency on Ruby for this to work + +>> bundle install +>> bundle exec jekyll serve --force_polling + +Open web browser to http://127.0.0.1:4000/ \ No newline at end of file diff --git a/_config.yml b/_config.yml index ea7d07e6d..ea995e5ef 100644 --- a/_config.yml +++ b/_config.yml @@ -8,22 +8,22 @@ plugins: #------------------------------- # General Site Settings -title: Johnny Hopkins -description: "Hi I'm Johnny, and I'm a Data Scientist. My portfolio focuses on interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!" +title: Antonia Iaquinta +description: "Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!" baseurl: "" # the subpath of your site, e.g. /blog url: "" # the base hostname & protocol for your site, e.g. http://example.com #------------------------------- # About Section -subtitle: Data Science Portfolio -location: "London, UK" -resume_url: /docs/resume.docx -avatar_image: /img/profile_picture.JPG +subtitle: Data Science & Analytics Portfolio +location: "Massachusetts, US" +resume_url: /docs/antonia_iaquinta_resume.docx +avatar_image: /img/my_headshot.jpg #------------------------------- # Contact links -linkedln: "https://linkedln.com/#" # Add your linkedln handle -github: "https://github.com/#" # Add your github handle +linkedin: "https://www.linkedin.com/in/antonia-iaquinta/" # Add your linkedln handle +github: "https://github.com/golden-hail/golden-hail.github.io" # Add your github handle paginate: 6 paginate_path: "/page/:num" @@ -40,15 +40,3 @@ defaults: read_time: true share: false - -#------------------------------- -# Exclude from processing. -# exclude: -# - Gemfile -# - Gemfile.lock -# - node_modules -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ - diff --git a/_posts/2021-06-09-Finding-Prime-Numbers-With-Python.md b/_posts/2021-06-09-Finding-Prime-Numbers-With-Python.md deleted file mode 100644 index 31d5defa6..000000000 --- a/_posts/2021-06-09-Finding-Prime-Numbers-With-Python.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -layout: post -title: Finding Prime Numbers with Python -image: "/posts/primes_image.jpeg" -tags: [Python, Primes] ---- - -In this post I'm going to run through a function in Python that can quickly find all the Prime numbers below a given value. For example, if I passed the function a value of 100, it would find all the prime numbers below 100! - -If you're not sure what a Prime number is, it is a number that can only be divided wholly by itself and one so 7 is a prime number as no other numbers apart from 7 or 1 divide cleanly into it 8 is not a prime number as while eight and one divide into it, so do 2 and 4 - -Let's get into it! - ---- - -First let's start by setting up a variable that will act as the upper limit of numbers we want to search through. We'll start with 20, so we're essentially wanting to find all prime numbers that exist that are equal to or smaller than 20 - -```ruby -n = 20 -``` - -The smallest true Prime number is 2, so we want to start by creating a list of numbers than need checking so every integer between 2 and what we set above as the upper bound which in this case was 20. We use n+1 as the range logic is not inclusive of the upper limit we set there - -Instead of using a list, we're going to use a set. The reason for this is that sets have some special functions that will allow us to eliminate non-primes during our search. You'll see what I mean soon... - -```ruby -number_range = set(range(2, n+1)) -``` - -Let's also create a place where we can store any primes we discover. A list will be perfect for this job - -```ruby -primes_list = [] -``` - -We're going to end up using a while loop to iterate through our list and check for primes, but before we construct that I always it valuable to code up the logic and iterate manually first. This means I can check that it is working correctly before I set it off to run through everything on it's own - -So, we have our set of numbers (called number_range to check all integers between 2 and 20. Let's extract the first number from that set that we want to check as to whether it's a prime. When we check the value we're going to check if it is a prime...if it is, we're going to add it to our list called primes_list...if it isn't a prime we don't want to keep it - -There is a method which will remove an element from a list or set and provide that value to us, and that method is called *pop* - -```ruby -print(number_range) ->>> {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} -``` -If we use pop, and assign this to the object called **prime** it will *pop* the first element from the set out of **number_range**, and into **prime** - -```ruby -prime = number_range.pop() -print(prime) ->>> 2 -print(number_range) ->>> {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} -``` - -Now, we know that the very first value in our range is going to be a prime...as there is nothing smaller than it so therefore nothing else could possible divide evenly into it. As we know it's a prime, let's add it to our list of primes... - -```ruby -primes_list.append(prime) -print(primes_list) ->>> [2] -``` - -Now we're going to do a special trick to check our remaining number_range for non-primes. For the prime number we just checked (in this first case it was the number 2) we want to generate all the multiples of that up to our upper range (in our case, 20). - -We're going to again use a set rather than a list, because it allows us some special functionality that we'll use soon, which is the magic of this approach. - -```ruby -multiples = set(range(prime*2, n+1, prime)) -``` - -Remember that when created a range the syntax is range(start, stop, step). For the starting point - we don't need our number as that has already been added as a prime, so let's start our range of multiples at 2 * our number as that is the first multiple, in our case, our number is 2 so the first multiple will be 4. If the number we were checking was 3 then the first multiple would be 6 - and so on. - -For the stopping point of our range - we specify that we want our range to go up to 20, so we use n+1 to specify that we want 20 to be included. - -Now, the **step** is key here. We want multiples of our number, so we want to increment in steps *of our* number so we can put in **prime** here - -Lets have a look at our list of multiples... - -```ruby -print(multiples) ->>> {4, 6, 8, 10, 12, 14, 16, 18, 20} -``` - -The next part is the magic I spoke about earlier, we're using the special set functionality **difference_update** which removes any values from our number range that are multiples of the number we just checked. The reason we're doing this is because if a number is a multiple of anything other than 1 or itself then it is **not a prime number** and can remove it from the list to be checked. - -Before we apply the **difference_update**, let's look at our two sets. - -```ruby -print(number_range) ->>> {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} - -print(multiples) ->>> {4, 6, 8, 10, 12, 14, 16, 18, 20} -``` - -**difference_update** works in a way that will update one set to only include the values that are *different* from those in a second set - -To use this, we put our initial set and then apply the difference update with our multiples - -```ruby -number_range.difference_update(multiples) -print(number_range) ->>> {3, 5, 7, 9, 11, 13, 15, 17, 19} -``` - -When we look at our number range now, all values that were also present in the multiples set have been removed as we *know* they were not primes - -This is amazing! We've made a massive reduction to the pool of numbers that need to be tested so this is really efficient. It also means the smallest number in our range *is a prime number* as we know nothing smaller than it divides into it...and this means we can run all that logic again from the top! - -Whenever you can run sometime over and over again, a while loop is often a good solution. - -Here is the code, with a while loop doing the hard work of updated the number list and extracting primes until the list is empty. - -Let's run it for any primes below 1000... - -```ruby -n = 1000 - -# number range to be checked -number_range = set(range(2, n+1)) - -# empty list to append discovered primes to -primes_list = [] - -# iterate until list is empty -while number_range: - prime = number_range.pop() - primes_list.append(prime) - multiples = set(range(prime*2, n+1, prime)) - number_range.difference_update(multiples) -``` - -Let's print the primes_list to have a look at what we found! - -```ruby -print(primes_list) ->>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997] -``` - -Let's now get some interesting stats from our list which we can use to summarise our findings, the number of primes that were found, and the largest prime in the list! - -```ruby -prime_count = len(primes_list) -largest_prime = max(primes_list) -print(f"There are {prime_count} prime numbers between 1 and {n}, the largest of which is {largest_prime}") ->>> There are 168 prime numbers between 1 and 1000, the largest of which is 997 -``` - -Amazing! - -The next thing to do would be to put it into a neat function, which you can see below: - -```ruby -def primes_finder(n): - - # number range to be checked - number_range = set(range(2, n+1)) - - # empty list to append discovered primes to - primes_list = [] - - # iterate until list is empty - while number_range: - prime = number_range.pop() - primes_list.append(prime) - multiples = set(range(prime*2, n+1, prime)) - number_range.difference_update(multiples) - - prime_count = len(primes_list) - largest_prime = max(primes_list) - print(f"There are {prime_count} prime numbers between 1 and {n}, the largest of which is {largest_prime}") -``` - -Now we can jut pass the function the upper bound of our search and it will do the rest! - -Let's go for something large, say a million... - -```ruby -primes_finder(1000000) ->>> There are 78498 prime numbers between 1 and 1000000, the largest of which is 999983 -``` - -That is pretty cool! - -I hoped you enjoyed learning about Primes, and one way to search for them using Python. - ---- - -###### Important Note: Using pop() on a Set in Python - -In the real world - we would need to make a consideration around the pop() method when used on a Set as in some cases it can be a bit inconsistent. - -The pop() method will usually extract the lowest element of a Set. Sets however are, by definition, unordered. The items are stored internally with some order, but this internal order is determined by the hash code of the key (which is what allows retrieval to be so fast). - -This hashing method means that we can't 100% rely on it successfully getting the lowest value. In very rare cases, the hash provides a value that is not the lowest. - -Even though here, we're just coding up something fun - it is most definitely a useful thing to note when using Sets and pop() in Python in the future! - -The simplest solution to force the minimum value to be used is to replace the line... - -```ruby -prime = number_range.pop() -``` - -...with the lines... - -```ruby -prime = min(sorted(number_range)) -number_range.remove(prime) -``` - -...where we firstly force the identification of the lowest number in the number_range into our prime variable, and following that we remove it. - -However, because we have to sort the list for each iteration of the loop in order to get the minimum value, it's slightly slower than what we saw with pop()! - - diff --git a/_posts/2026-05-15-Prime-Numbers-Finder.md b/_posts/2026-05-15-Prime-Numbers-Finder.md new file mode 100644 index 000000000..2b79a5625 --- /dev/null +++ b/_posts/2026-05-15-Prime-Numbers-Finder.md @@ -0,0 +1,175 @@ +--- +layout: post +title: Prime Numbers Finder with Python +image: "/posts/prime_nums.jpg" +tags: [Python, Primes] +--- + +Hello! In this post, I'll be walking through a Python function I wrote that returns a list of all prime numbers that are below a specified input number, **n**. For example, if we passed in a value n=10, this function would return a list of all prime numbers below the specified number 10! + +For reference, a prime number is an integer that can only be divided wholly by itself and 1. In our example of n=10, we would expect an output as follows: +```python +>>> {2, 3, 5, 7} +``` + +--- +Before we jump into the final script, we'll be walking through each functional step of the code. In the end, we'll have a while loop that loops through a range of numbers up to the defined number, **n** that is able to return a list of prime numbers within that range. + +First, let's set our input value **n**, the number in which we want to find all prime numbers up to. + +```python +n = 20 +``` + +Next, let's define the range of values, up to n = 20. + +The lowest prime number to exist is 2. Because of this, we want our initial list of possible prime numbers, **number_range**, to be a list of numbers starting from 2 and ending at specified input "n". + +Instead of an ordinary list, however, we'll be using a set for our number_range. Sets are a good choice as sets are faster than lists and contain unique values, however, Stay tuned to find out why! + +```python +number_range = set(range(2, n+1)) +>>> {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} +``` +The end goal is to loop through this number_range and extract each value from this set with the *pop* method to determine whether each value is a prime number. If the routine determines a value is prime, we'll append it onto the new **primes_list** list. Let's initialize that empty list! + +```python +primes_list = [] +``` + +The *pop* method removes an element from a list or set, and returns that value to us. 2 is the first value in our number_range, and is the lowest prime number to exist. Let's pop it out and assign it to **prime** + +```python +prime = number_range.pop() +print(prime) +>>> 2 +print(number_range) +>>> {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} +``` + +After the pop is applied, value 2 is no longer in our number_range set. We'll then add this first prime number **prime** to our empty **primes_list** + +```python +primes_list.append(prime) +print(primes_list) +>>> [2] +``` + +All multiples of 2 are by default not prime numbers, since they can be divided by 2 in addition to itself and 1. Let's define a new set for all multiples of 2 that are lower than the defined n=20 value. + +```python +multiples = set(range(prime*2, n+1, prime)) +``` + +The range was determined as follows: range(start, stop, step) + +For the START in our range, we can start with prime*2, which will be the first multiple of the prime number. +For the STOP in our range, we want to stop at the index of our number n=20, which is the value of n+1. +For the STEP in our range, we want multiples of our prime, so we want to increment in steps of this popped out prime number so we can put in our **prime** list. + +Now we can see that this range worked in our output as we have all multiples of 2 up to our n value of 20! + +```python +print(multiples) +>>> {4, 6, 8, 10, 12, 14, 16, 18, 20} +``` + +Here's where the magic happens! Why did we use sets? With sets, we can use a special functionality called **difference_update**. + +Before we apply the **difference_update** method, let's look at our two sets again. + +```python +print(number_range) +>>> {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} + +print(multiples) +>>> {4, 6, 8, 10, 12, 14, 16, 18, 20} +``` + +**difference_update** will be used to update our number_range by dropping the values that exist in the **multiples** list we defined. + +```python +number_range.difference_update(multiples) +print(number_range) +>>> {3, 5, 7, 9, 11, 13, 15, 17, 19} +``` + +Our number_range list is now trimmed down to not include multiples of 2, as we've determined the **multiples** values are not prime. This vastly reduces how many numbers in the number_range we have left to assess. The same steps above can now be applied to the the first number in the new number_range set, 3. We would pop 3 into the primes_list, find all multiples of 3 up to value **n=20**, and then difference_update to remove the non-prime multiples from our number_range set. + +We now have all the pieces to make this loop work, where we can define **n** and have the script return the **primes_list** on it's own! + +Let's assemble our while loop and up the ante, setting our **n** value to 1000: + +```python +n = 1000 + +# define number range +number_range = set(range(2, n+1)) + +# create empty list to append prime numbers to +primes_list = [] + +# iterate until the number_range set is empty +while number_range: + prime = number_range.pop() + primes_list.append(prime) + multiples = set(range(prime*2, n+1, prime)) + number_range.difference_update(multiples) + +print(primes_list) +>>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997] +``` + +Success! Now let's throw this all into a function. + +A list of the prime numbers was our goal, however, we can also return a few more details to the user about the extracted primes_list. We can print the amount of prime numbers found in our range as well as what the highest prime number is in the set. + +```python +def primes_finder(n): + + # define number range + number_range = set(range(2, n+1)) + + # create empty list to append prime numbers to + primes_list = [] + + # iterate until the number_range set is empty + while number_range: + prime = number_range.pop() + primes_list.append(prime) + multiples = set(range(prime*2, n+1, prime)) + number_range.difference_update(multiples) + + # count how many primes in primes_list output + prime_count = len(primes_list) + # find the largest prime number + largest_prime = max(primes_list) + # print the details + print(f"There are {prime_count} prime numbers between 1 and {n}, the largest of which is {largest_prime}") + + >>> There are 168 prime numbers between 1 and 1000, the largest of which is 997 +``` + +With our function now defined, it's as easy as running **primes_finder(n)** where n can be any numerical value of choice. + +```python +primes_finder(1000000) +>>> There are 78498 prime numbers between 1 and 1000000, the largest of which is 999983 +``` + +And there we have it! Feel free to copy the function and try it for yourself! :) + +**A NOTE ON POP():** + +In this script, we demonstrated the **pop** functionality, however, using pop() on a set could be risky in some instances. + +Pop() extracts the lowest value of a list or set, which seems confusing since sets are unordered by definition. Under the Python covers, however, the elements in the set are internally stored with an order determined by the hash code of each element. Occassionally, this hash may provide a value that is not the lowest. + +To safeguard against this, we can replace the definition of of **prime** value to from *prime = number_range.pop()* to.. + +```python +prime = min(sorted(number_range)) +number_range.remove(prime) +``` + +This new definition of prime ascertains that we are always pulling the lowest value out of our number_range. This method, however, is slower as we are sorting the number_range set each time we define **prime**. diff --git a/_posts/2026-06-12-Earthquake-Tracker-Tableau.md b/_posts/2026-06-12-Earthquake-Tracker-Tableau.md new file mode 100644 index 000000000..11036b0b4 --- /dev/null +++ b/_posts/2026-06-12-Earthquake-Tracker-Tableau.md @@ -0,0 +1,75 @@ +--- +layout: post +title: Earthquake Tracker - Tableau Dashboard +image: "/posts/quake_dash.jpg" +tags: [Tableau, Data Viz] +--- + +This interactive Tableau dashboard tracks global earthquake activity for a given data set of a 30-day period. + +***NOTE:** Due to formatting troubles between Tableau, Ruby, and git-pages, it is recommended to view the dashboard on Tableau Public itself by clicking "View on Tableau Public" on the bottom left of the dashboard below.* + +* Take in the overall data summary for earthquakes over a 30-day period. +* See the frequency of earthquakes in each location as well as the average and maximum magnitude +* For finer detail, scroll for a summary of particular days in the upper right hand slide bar +* Hover your mouse over the data points on the map to easily see earthquake information, such as locations and magnitudes for each recorded event + +___ + + +
+ + +
+ + +___ + +# **Data Disclaimer**: +This earthquake data in this dashboard is not realistic whatsoever. The frequency of high magnitude quakes are drastically exaggerated to provide diverse datapoints to demonstrate data visualization in Tableau. If we were to receive legitimate data from an agency like the USGS, we'd be able to simply upload the real world data into our proof-of-concept dashboard + +### Fun Fact(s): +* The greatest magnitude of an earthquake ever recorded was in Chili in 1960 at a magnitude of 9.5. +* Earthquakes cannot exceed a magnitude of 10 because no tectonic fault line is long enough to produce one. A magnitude 10 earthquake would require a fault wrapping all the way around the equator +* The USGS (United States Geological Survey) estimates an average of 16 major earthquakes per year, with a magnitude of 7.0 or higher diff --git a/_posts/2026-06-30-Chi-Square-Test.md b/_posts/2026-06-30-Chi-Square-Test.md new file mode 100644 index 000000000..05173a03d --- /dev/null +++ b/_posts/2026-06-30-Chi-Square-Test.md @@ -0,0 +1,287 @@ +--- +layout: post +title: Assessing Campaign Performance Using Chi-Square Test For Independence +image: "/posts/AB_testing.jpg" +tags: [AB Testing, Hypothesis Testing, Chi-Square, Python] +--- + +In this project, we'll be running an A/B test on grocery retailer campaign data to determine if the quality of the promotion mail sent to customers significantly impacted signup rates for a promoted "Delivery Club" membership. + +We will do this through applying the Chi-Square Test of Independence to measure the significance of the difference in signups between 2 groups of customers. + +# Table of contents + +- [00. Project Overview](#overview-main) + - [Context](#overview-context) + - [Actions](#overview-actions) + - [Results & Discussion](#overview-results) +- [01. Concept Overview](#concept-overview) +- [02. Data Overview & Preparation](#data-overview) +- [03. Applying Chi-Square Test For Independence](#chi-square-application) +- [04. Analyzing The Results](#chi-square-results) +- [05. Discussion](#discussion) + +___ + +# Project Overview + +
+### Context + +In June, a grocery retailer client ran a campaign to promote their new "Delivery Club" memberships. Signing up for the club costs $100 and gives customers free grocery deliveries for one year, starting July 1st. + +For the campaign promoting the club, customers were put randomly into three groups: +* The first group received a bland, low cost mailer, Mailer 1. +* The second group received a colorful, high quality, high cost mailer, Mailer 2. +* The third group was a control group. They did not receive any mail. + +The client knows that customers who were contacted, signed up for the Delivery Club at a far higher rate than the control group, but are now curious as to if there is a significant difference in customer signup rate between the cheap mailer and the expensive mailer. This will allow them to make more informed decisions in the future, such as whether it is worth it to spend the money on higher cost mailers or not. + +
+ +### Actions + +The Chi-Square Test of Independence will be applied to compare the **signup rates** of two distinct groups from our *campaign_data* client database: +* Customers who received low cost **Mailer1** +* Customers who received the higher quality, high cost **Mailer2**. + +The Hypothesis Test elements are defined as: +* **Null Hypothesis:** There is no relationship between mailer type and signup rate. They are independent variables and any difference in signup rates are due to chance. +* **Alternate Hypothesis:** There is a true relationship between mailer type and signup rate. They are not independent. +* **Significance Level:** 0.05 (5%) + +Through Pandas, we can aggregate the observed data of *mailer_type* and *signup_flag* into a 2x2 matrix. We'll then feed these observed frequencies to the `chi2_contingency` algorithm provided by the `scipy` library to calculate the expected values, p-values, Degrees of Freedom (dof), and the Chi-Square Statistic. + +We'll then take this one step further and find our Critical Value to truly get a grasp on whether we should reject or fail to reject our null hypothesis. + +*See more details on the Chi-Square Test, and other related concepts in the *Concept Overview* section* + +**Note**: Another viable choice for an approach comparing *rates* would be *Z-Test For Proportions*, which would provide us with the same statistical result as the Chi-Square Test would. However, in this business case, it was determined to be more beneficial to use the Chi-Square Test for analysis. + +The Chi-Square Test can be represented using a 2x2 data matrix, making the data easier to visualize and explain to stakeholders. Additionally, if the company ever had interest in expanding this campaign to more than two groups, we could easily adapt the Chi-Squared approach to include new group data, providing the business with a consistent way to measure variable significance. + +
+ +### Results & Discussion + +At a first glance of the observed data, it appears that the high-cost mailer yielded a higher signup rate: + + * Mailer 1 (low-cost): 32.8% signup rate + * Mailer 2 (high-cost): 37.8% signup rate + +However, the results of the Chi-Square Test of Independence indicate otherwise: + + * Chi-Square Statistic: 1.72 < Critical Value: 3.84 + * p-value: 0.19 > Acceptance Criteria 0.05 + +With these results, we retain the null hypothesis and conclude there is no measurable relationship between mailer cost and signup rates. This suggests that the 5% variation in signup rate could be due to random chance. + +**Business Impact:** +Upgrading to the higher-cost mailer risks increasing spending without reliably driving additional revenue. + +**Next Steps:** +As current findings do not support a permanent shift to higher-cost mailers, we advise running additional A/B tests with larger sample sizes to confirm whether a true performance gap exists or not. + +___ + +# Concept Overview +
+### A/B Testing + +An A/B test takes two randomized groups, A and B, and provides them with different experiences. In the A/B test, we measure the response of both groups to understand the impact each experience had on the response. + +For example, a company may post 2 different pictures advertising the same product on their website. With an A/B test, we could look to measure if the picture used in the ad significantly impacted the number of users who clicked on the ad. If one ad yielded significantly more clicks, the business can use this data when thinking about what characteristics got the user to click and incorporate those features into future ads. + +
+### Hypothesis Testing + +A Hypothesis Test is a statistical method used to evaluate the likelihood of an assumption on a population, using sample data. It determines whether an observed pattern or correlation in the data is due to a true relationship or random chance. + +There are multiple types of Hypothesis Tests as well as many scenarios we can run them on. + +When performing any Hypothesis Test, the following must always be defined: + +* **The Null Hypothesis** + + The "Null Hypothesis" is a statistical assumption stating that there is no statistically significant relationship, association, or difference between two outcomes or groups. We run a Hypothesis Test to either reject or support this Null Hypothesis. + +* **The Alternate Hypothesis** + + The "Alternate Hypothesis" suggests that there is a significant and measurable relationship, effect, or difference between variables, directly contradicting the Null Hypothesis. When we reject the Null Hypothesis, we accept the Alternate Hypothesis, concluding that the observed relationship is highly unlikely to have occurred by chance alone. + +* **The Significance Level** + + To statistically determine whether to reject the null hypothesis in favor of the alternate, a "significance level" must be specified. The *significance level* is a specified p-value threshold in which we are measuring our null hypothesis against. In other words, the set threshold draws a line between what we consider random chance and what we consider a statistically significant result. + + A *p-value*, or probability value, is a calculated value used to determine if the data is extreme enough to reject the null hypothesis. It is a common practice to set the significance level to 0.05 or 5%. + + * A *low p-value* (≤ 0.05) suggests your results are highly unlikely to have occurred by chance. There is strong evidence to reject the null hypothesis as the relationship is statistically significant + + * A *High p-value* (> 0.05) means your results could easily happen under random variation. We would fail to reject the null hypothesis, meaning there isn't enough evidence to prove a true association exists + +
+#### Chi-Square Test of Independence + +The **Chi-Square Test of Independence** is a hypothesis test used to determine whether a significant association exists between two categorical variables. It compares the *observed frequencies* from the actual data points collected from a sample against the *expected frequencies*, the data expected to be seen if the two variables were truly independent. + +The *Null Hypothesis* described above is our baseline assumption. It assumes that there is no relationship or difference between the two variables. It asserts that the observed frequencies of data will match the expected frequencies, with any minor difference being the result of random chance. + +The Chi-Square Contingency function provides a Chi-Square statistic that can be compared against a calculated *critical value*, in order to reject or fail to reject a null hypothesis. + +* Chi-Square Statistic < Critical Value: We retain the null hypothesis - the observed results could easily happen under random variation, meaning there isn't enough evidence to prove a true association exists between variables + +* Chi-Square Statistic ≥ Critical Value: We reject the null hypothesis - the observed results are highly unlikely to have occurred by chance providing strong evidence that an association exists between variables + +This function also provides a p-value, which can be compared to the chosen significance level (commonly set to 0.05). If the p-value is less than or equal to this significance level, the null hypothesis is rejected. + +___ + +# Data Overview & Preparation + +Our table of interest in the grocery client database is the *campaign_data* table. +This table contains each unique customer_id, the type of mailer they received, if any, and whether or not the customer signed up for the Delivery Club membership. + +To determine whether the fancier Mailer 2 led to a significant difference of people to sign up as opposed to the cheaper Mailer 1, we will first need to exclude the control group from the data by extracting the customers who got either mailer. + +```python +# import the required python libraries +import pandas as pd +from scipy.stats import chi2_contingency, chi2 + +# import campaign data +campaign_data = pd.read_excel(...) + +# filter out the control group +campaign_data = campaign_data.loc[campaign_data["mailer_type"] != "Control"] +``` +
+Below is a 10 row sample of the imported **campaign_data** DataFrame: +
+ +| **customer_id** | **campaign_name** | **mailer_type** | **signup_flag** | +|---|---|---|---| +| 74 | delivery_club | Mailer1 | 1 | +| 524 | delivery_club | Mailer1 | 1 | +| 607 | delivery_club | Mailer2 | 1 | +| 343 | delivery_club | Mailer1 | 0 | +| 322 | delivery_club | Mailer2 | 1 | +| 115 | delivery_club | Mailer2 | 0 | +| 1 | delivery_club | Mailer2 | 1 | +| 120 | delivery_club | Mailer1 | 1 | +| 52 | delivery_club | Mailer1 | 1 | +| 405 | delivery_club | Mailer1 | 0 | +| 435 | delivery_club | Mailer2 | 0 | + +In the **campaign_data** DataFrame we have the following columns: + +* customer_id +* campaign name +* mailer_type (either Mailer1 or Mailer2) +* signup_flag (either 1 or 0) + +___ + +# Applying Chi-Square Test of Independence + +
+ +#### State Hypotheses & Significance Level For Test + +To kick off our Hypothesis Test, we'll need to define our **Null Hypothesis**, our **Alternate Hypothesis**, and our **Significance Level**. (See more on these terms in the *Concept Overview* section above) + +For our significance level, we'll be using the commonly used value of 0.05 (or 5%). + +* null_hypothesis: There is no relationship between mailer type and signup rate. They are independent. +* alternate_hypothesis: There is a relationship between mailer type and signup rate. They are not independent. +* significance_level: 0.05 + +
+#### Calculate Observed Frequencies & Expected Frequencies + +As detailed in the *Concept Overview* section above, the **observed frequencies** come directly from the rates per group in our collected data. To get these frequencies, we'll create our 2x2 matrix needed for the Chi-Square approach, using a method called **`crosstab()`**. + +Our observed values come directly from our campaign_data imported above. We are analyzing the impact that mailer_type had on member signup rates, so we'll want to pass these data points into the crosstab method. + +We can visualize this data in the `DataFrame` below: +```python +observed_vals = pd.crosstab(campaign_data['mailer_type'],campaign_data['signup_flag']) + +print(observed_vals) +>>> signup_flag 0 1 +>>> mailer_type +>>> Mailer1 252 123 +>>> Mailer2 209 127 +``` + +By running the `crosstab` method, we see: +* For customers who received Mailer 1, + * 252 customers did not sign up for the promotion + * 123 customers signed up for the promotion. +* For customers who received Mailer 2, + * 209 customers did not sign up for the promotion + * 127 customers signed up for the promotion + +The Chi-Squared contingency function won't accept Pandas DataFrames, so we'll have to convert the observed values data into an array by using *.values* property for observed_values. + +```python +observed_values = pd.crosstab(campaign_data['mailer_type'],campaign_data['signup_flag']).values +print(observed_values) +>>> array([[252, 123], + [209, 127]]) +``` + +Now that the observed_values are in an array, we can pass the observed_values into our `chi2_contingency` function that we imported with `scipy`. + +Running the Chi-Squared function will provide us with the following: +* **Expected values** +* **P-values** +* **Degrees of Freedom (dof)**: used for finding the critical value later +* **Chi2 Statistic** + +We can additionally test the null hypothesis by finding the *critical value* along our Chi-Squared distribution based on our set significance level and our calculated dof, using the `chi2.ppf` percentage point function from the scipy library. + +```python +# run the chi-square test +chi2_statistic, p_value, dof, expected_values = chi2_contingency(observed_values, correction = True) + +print(chi2_statistic) +>> 1.72 + +print(p_value) +>> 0.19 + +# find the critical value for our test using chi2.ppf +critical_value = chi2.ppf(1 - significance level, dof) + +print(critical_value) +>> 3.84 +``` + +*Note: The chi2_contingency function accepts a correction parameter. By setting correction = True, we are applying Yate's Correction, which is applicable when comparing 2 group totals against their results in a 2x2 matrix* + +With that code in place, we have all of the values necessary to analyze the results for our A/B test and draw a conclusion. + +___ + +# Analyzing The Results + +Based upon the raw observed values from the campaign, we can see that the signup rate for customers receiving Mailer 2 was higher than for the customers receiving Mailer 1: + +* Mailer 1 (low-cost): 32.8% signup rate +* Mailer 2 (high-cost): 37.8% signup rate + +The Chi-Square test gives us further insight into whether this difference in signup rate was truly due to the quality of the mailer, or just due to chance. + +Our calculated p-value of 0.19 is greater than our set significance level of 0.05, meaning that **the difference in signup outcomes between the two mailing groups is not significant.** + +This conclusion is further supported by our Chi-Square statistic of 1.72 being lower than the calculated critical value of 3.84. In both cases, we **retain the null hypothesis** as there is not enough evidence that the signup rates for Mailer 1 and Mailer 2 were significantly different. + +___ + +# Discussion + +While Mailer 2 yielded a higher signup rate than Mailer 1 (37.8% vs. 32.8%), the difference was not statistically significant at our 0.05 significance level. The results of the Chi-Square test of independence reveal that we cannot yet conclude whether the more expensive mailer had a true impact on whether a customer signed up. + +At first glance, the client might look at the raw percentages and assume the premium quality of Mailer 2 was the driving force behind the higher signup rate. Without this hypothesis test, they may have considered exclusively sending the more expensive mailers for their next promotion, potentially wasting valuable funding. + +It is important to note that this analysis was limited to a small sample size from a single campaign. *Failing to reject the null hypothesis does not definitively prove that mailer quality didn't impact signup rates, it simply means we currently lack the evidence to prove that it does.* Therefore, we advise the client not to jump to conclusions just yet. Running additional A/B tests over time will provide deeper insights. If future data consistently supports retaining the null hypothesis, we can then confidently recommend sticking to the cheaper mailers to optimize printing costs. \ No newline at end of file diff --git a/_posts/2026-08-01-E-Commerce-AB-Test.md b/_posts/2026-08-01-E-Commerce-AB-Test.md new file mode 100644 index 000000000..b6b5b7c97 --- /dev/null +++ b/_posts/2026-08-01-E-Commerce-AB-Test.md @@ -0,0 +1,301 @@ +--- +layout: post +title: Customer Checkout UI Redesign A/B Test Analysis +image: "/posts/checkout_UI.jpg" +tags: [AB Testing, Hypothesis Testing, Z-Test, Shapiro-Wilk, Mann-Whitney U, Python] +--- + +Can a checkout redesign boost conversion rates without lowering average order values? In this case study, we evaluate a 30-day e-commerce A/B test using a triad of statistical hypothesis tests - combining a Two-Sample Z-Test for Proportions, Shapiro-Wilk normality testing, and a Mann-Whitney U Test—to deliver a data-backed rollout recommendation. + +___ + +# Table of Contents + +- [00. Project Overview](#overview-main) + - [Context](#overview-context) + - [Actions](#overview-actions) + - [Results & Discussion](#overview-results) +- [01. Data Overview & Preparation](#data-overview) +- [02. Applying Z-Test for Proportions](#z-test-application) +- [03. Applying Shapiro-Wilk to Assess for Data Normality](#shapiro-wilk) +- [04. Applying Mann-Whitney U Test](#mann-whitney) +- [05. Analyzing The Results](#Z-test-results) +- [06. Discussion](#discussion) + +___ + +# Project Overview +
+### Context + +An e-commerce retailer redesigned their customer checkout user interface (UI) in an effort to streamline the cart-to-purchase process and improve checkout conversion rates. + +The campaign was run across a 30-day testing window where incoming site traffic was randomly split between two groups: +- The `Control Group` used the legacy UI to complete purchases +- The `Test Group` used the new redesigned UI to complete purchases + +The product and marketing teams logged daily performance metrics including number of items added to carts, number of completed purchases, and the total spent by customers on a day-to-day basis. + +**Objective:** Determine if the redesigned UI significantly improves purchase conversion rates without negatively impacting Average Order Value (AOV). + +
+ +### Actions + +To evaluate the performance of the UI redesign, as well as the impact on Average Order Value (AOV), we structured our analysis across three sequential hypothesis tests: + +1. **Primary Metric Evaluation (Conversion Rate):** + * Objective: Determine whether the new checkout UI drives a statistically significant lift in cart-to-purchase conversion rates. + * Method: Run a Two-Sample Z-Test for Proportions with `statsmodels`. + +2. Normality Diagnostic (Daily Average Order Value): + * Objective: Evaluate whether daily Average Order Value (spent/purchased) meets the parametric assumption of a normal distribution. + * Method: Run a Shapiro-Wilk Test on daily AOV for both Control and Test groups using `scipy.stats`. + +3. Secondary Metric Evaluation (AOV Impact) + * Objective: Assess whether customer spending per completed order changed significantly between UI variants. + * Method: Select the final two-sample test based on the Shapiro-Wilk diagnostic. If the data is normally distributed, run the Welch's *t*-Test. If the AOV data is non-parametric, run the Mann-Whitney U Test with. Use `scipy.stats`. + +
+ +### Results & Discussion + +Our 30-day A/B experiment confirmed that the redesigned checkout UI significantly improves purchase completion without harming average order value. + +* Conversion Rate Lift: Cart-to-purchase conversion increased from 40.21% (Control) to 59.13% (Test) with a +47.03% relative improvement. Z-Test of Proportions confirmed this difference is statistically significant (p-value [0.0000] < significance level [0.05]) +* Average Order Value (AOV): AOV saw a nominal increase of +$0.51 ($102.14 vs. $102.65), but non-parametric testing confirmed this difference is not statistically significant (p-value [0.2717] > significance level [0.05]). + +**Business Recommendation:** Proceed with the complete rollout the redesigned UI to all customers. With the split-testing routing infrastructure already in place, the engineering effort to fully deploy the UI is minimal and carries negligible risk. Continue with further metric testing as detailed in the *Discussion* section of this report. + +___ + +# Data Overview & Preparation +
+We'll start by importing our two groups of data, the `control` group who used the legacy UI and the `test` group who used the new UI for purchasing items in their carts. This data contains sales metrics on each unique day during the 30 day trial in August. + +```python +import pandas as pd + +control = pd.read_csv('control_group.csv', sep = ';') +test = pd.read_csv('test_group.csv', sep = ';') + +control = control.dropna() +test = test.dropna() +``` + +We have the following columns of interest in both datasets: +* Date +* Spend [USD] +* \# of Add to Cart +* \# of Purchase + +Before applying statistical analyses, let's simply aggregate the raw data to determine the conversion rates of items being put into carts to being purchased. + +```python +ctrl_conversion_rate = round(control['# of Purchase'].sum() / control['# of Add to Cart'].sum() * 100, 3) +test_conversion_rate = round(test['# of Purchase'].sum() / test['# of Add to Cart'].sum() * 100, 3) + +relative_lift = (test_conversion_rate - ctrl_conversion_rate)/ctrl_conversion_rate * 100 + +print(f'Control Group Conversion Rate = {ctrl_conversion_rate}%') +print(f'Test Group Conversion Rate = {test_conversion_rate}%') +print(f'Relative Lift = {relative_lift:.2f}%') + +>> Control Group Conversion Rate = 40.215% +>> Test Group Conversion Rate = 59.128% +>> Relative Lift = 47.03% +``` + +At first glance, basic conversion rate data aggregation suggests the newly deployed UI improved checkout performance, driving a +47.03% relative lift in conversion rate (rising from 40.22% in the Control group to 59.13% in the Test group). + +Looking at the average order value (AOV) data, we seemed to only have a $0.51 increase in revenue, which so far suggests that the new UI did not negatively impact sales. + +```python +ctrl_AOV = round(control['Spend [USD]'].sum()/control['# of Purchase'].sum(), 2) +test_AOV = round(test['Spend [USD]'].sum()/test['# of Purchase'].sum(), 2) + +print(f'Control AOV = ${ctrl_AOV}') +print(f'Test AOV = ${test_AOV}') + +>> Control AOV = $4.41 +>> Test AOV = $4.92 +``` + +However, raw descriptive statistics alone cannot determine whether these gains are statistically meaningful or the result of random sampling noise. To establish whether the redesigned UI genuinely drives conversion rate improvements, we evaluate these proportions using a Two-Sample Z-Test for Proportions. + +___ + +# Applying Z-Test for Proportions + +
+ +### State Hypotheses & Significance Level For Test + +To kick off our Z-Test, we'll need to define our **Null Hypothesis**, our **Alternate Hypothesis**, and our **Significance Level**. For our significance level, we'll be using the commonly used value of 0.05 (or 5%), which will be carried through for all subsequent tests. + +* **Null Hypothesis:** There is no significant relationship between the checkout UI version and the sales conversion rate. They are independent. +* **Alternate Hypothesis:** There is a relationship between the checkout UI version and the sales conversion rate. They are not independent. +* **Significance Level:** 0.05 + +
+ +## Calculating the P-Value + +We want to look at if the new UI led to a significant increase in conversion rate, from items added to the cart to the purchase step. + +``` +* If p-value >= 0.05: Fail to reject the null hypothesis. UI version and conversion rate are statistically independent +* If p-value < 0.05: Reject the null hypothesis in favor of the alternate +``` + +To do this, we will `statsmodels.stats.proportion` library was used to import the `proportion_ztest` algorithm, to run our Z-Test. The results of this test will provide a p-value to be compared against our significance level. + +Inputs of the proportions_ztest: +--`count` represents the amount of successes for each dataset: it will be defined as the number of total purchases from each dataset. +--`nobs (ie. Number of Observations)` will be the number of items added to carts. +--`alternative`, looking if the % of signups is significantly higher, or *larger* + +```python +import numpy as np +from statsmodels.stats.proportion import proportions_ztest + +purchases = [test["# of Purchase"].sum(), control["# of Purchase"].sum()] +carts = [test["# of Add to Cart"].sum(), control["# of Add to Cart"].sum()] + +z_stat, p_val = proportions_ztest( + count=purchases, nobs=carts, alternative="larger" +) + +print(f"Z-statistic: {z_stat:.4f}") +print(f"p-value: {p_val:.4f}") + +>> Z-statistic: 47.1959 +>> p-value: 0.0000 +``` + +Our calculated p-value of 0.0000 is less than our set significance level of 0.05, which provides evidence to reject the null hypothesis in favor of the alternate. The 18.92% jump in cart-to-purchase conversion rate is statistically significant and virtually impossible to have happened by random chance! + +___ + +# Applying Shapiro-Wilk to Assess for Data Normality + +
+ +To assess the effect the UI version had on the Average Order Value (AOV), we first need to determine whether to run a parametric or a non-parametric test through another hypothesis test known as the Shapiro-Wilk test. + +* **Null Hypothesis:** The daily AOV data in both groups is normally distributed +* **Alternate Hypothesis:** The daily AOV data in both groups is not normally distributed +* **Significance Level:** 0.05 + +Parametric tests such as the standard two-sample *t*-test rely on the assumption of data normality to calculate standard errors and p-values; violating this assumption risks inflating Type I error rates. Running the Shapiro-Wilk test allows us to verify the data structure before selecting a model. + +## Calculating the P-Value + +``` +* If p-value >= 0.05: The daily AOV in both groups is normally distributed. Run Welch's *t*-test to assess statistical significance +* If p-value < 0.05: The daily AOV data is not normally distributed. Run the Mann Whitney U to assess statistical significance +``` + +We'll acquire the Shapiro-Wilk p-value outputs with the `scipy` library `stats` module. + +```python +from scipy import stats + +# Calculate AOV +control["AOV"] = control["Spend [USD]"] / control["# of Purchase"] +test["AOV"] = test["Spend [USD]"] / test["# of Purchase"] + +# Run Shapiro-Wilk on AOV +stat_ctrl, p_ctrl = stats.shapiro(control["AOV"]) +stat_test, p_test = stats.shapiro(test["AOV"]) + +print(f"Control AOV - W Stat: {stat_ctrl:.4f}, p-value: {p_ctrl:.4f}") +print(f"Test AOV - W Stat: {stat_test:.4f}, p-value: {p_test:.4f}") + +>> Control AOV - W Stat: 0.9132, p-value: 0.0206 +>> Test AOV - W Stat: 0.8966, p-value: 0.0069 +``` + +The p-values returned from the Shapiro-Wilk test are less than the significance level of 0.05 for both groups, indicating that the AOV data is not normally distributed. + +Consequently, we proceed with the non-parametric Mann-Whitney U test, which compares distribution ranks rather than sample means and requires no distributional assumptions. + +___ + +# Applying Mann-Whitney U Test + +We've now determined to run the Mann-Whitney U Test to assess our AOV between the control and test data. + +* **Null Hypothesis:** There is no statistical difference in the distribution of daily AOV between the Control and Test groups +* **Alternate Hypothesis:** There is a statistically significant difference in the distribution of daily AOV between the Control and Test groups +* **Significance Level:** 0.05 + +## Calculating the P-Value + +``` +* If p-value >= 0.05: There is statistically no difference in daily AOV between groups. Fail to reject the null hypothesis. +* If p-value < 0.05: There is a statistically significant difference in daily AOV between groups. Reject the null hypothesis. +``` + +With `scipy` `stats` already imported, we can run the `mannwhitneyu` algorithm on our data, then compare the returned p-value to our initial significance level. + +**Inputs of mannwhitneyu:** +--`control["AOV"]` +--`test["AOV"]` +--`alternative`: *two-sided* since we are looking at the difference between the control and test AOV. + +```python +u_stat, u_pvalue = stats.mannwhitneyu( + test["AOV"], control["AOV"], alternative="two-sided" +) + +print(f"U-statistic: {u_stat:.4f}") +print(f"p-value: {u_pvalue:.4f}") + +>> U-statistic: 508.0000 +>> p-value: 0.2717 +``` + +The returned p-value for the Mann-Whitney U test is greater than our significance level, thus, we fail to reject the null hypothesis. + +Based on the 30-day trial, there is no statistically significant difference in AOV across the two groups. + +___ + +# Analyzing The Results + +Through the **Z-Test of Proportions**, we calculated: + +``` +p-value [0.0000] < significance level [0.05] +``` + +Thus, we reject the null hypothesis in favor of the alternate - indicating that there is a true relationship between the new UI and the increase in conversion rate, and that the observed relative lift was not due to chance. + +
+ +Through the **Mann-Whitney U test** used to assess the statistical impact the new UI had on AOV, we determined the following: + +``` +p-value [0.2717] > significance level [0.05] +``` + +Thus, we fail to reject the null hypothesis - indicating that the new UI led to no statistically significant impact on AOV. + +
+ +**Conclusion:** We can statistically conclude that the new UI led to more items purchased while not negatively impacting AOV. + +___ + +# Discussion + +Our 30-day A/B experiment confirms that the redesigned checkout UI delivered a statistically significant boost in conversion rate without degrading customer average order values. + +Cart-to-purchase conversion increased from 40.21% (Control) to 59.13% (Test), representing a +47.03% relative lift in checkout efficiency. While raw AOV showed a slight increase of $0.51 cents per order, non-parametric testing confirmed this difference is not statistically significant with a p-value of 0.2717. + +**Business Impact:** These statistical conclusions support the business decision to roll out the redesigned UI to all customers. With the split-testing routing infrastructure already in place, the engineering effort to fully deploy the UI is minimal and carries negligible risk. + +**Next Steps:** +* **Monitor Long-Term AOV Trends Post-Rollout:** Higher cart conversion in theory lays the groundwork for revenue growth over time. We recommend tracking AOV and total revenue across a 60–90 day post-launch window to evaluate whether increased purchase frequency translates into higher revenue. +* **Analyze Behavioral Flow Features for Website-wide Applicability:** Analyze the new UI features (such as simplified fields, button placements, color schemes, widgets, etc) to determine which design features drove the highest lift. Then, identify ways to apply these effective design choices to other areas of the website. diff --git a/_posts/2026-08-15-Causal-Impact-Analysis.md b/_posts/2026-08-15-Causal-Impact-Analysis.md new file mode 100644 index 000000000..8ec245df3 --- /dev/null +++ b/_posts/2026-08-15-Causal-Impact-Analysis.md @@ -0,0 +1,289 @@ +--- +layout: post +title: Quantifying Sales Uplift With Causal Impact Analysis +image: "/posts/ci_plot.jpg" +tags: [Causal Impact Analysis, Python] +--- + +Our grocery retailing client is back with another request! Following our initial [Chi-Square Test of Independence](https://golden-hail.github.io/2026/06/30/Chi-Square-Test.html) test evaluating membership sign-up rates, leadership now wants to understand how the "Delivery Club" campaign impacted overall customer spending. In this project, we apply Causal Impact Analysis to quantify the net incremental revenue generated by the campaign and isolate true sales uplift from organic baseline trends. + +___ + +# Table of Contents + +- [00. Project Overview](#overview-main) + - [Context](#overview-context) + - [Actions](#overview-actions) + - [Results & Discussion](#overview-results) +- [01. Concept Overview](#concept-overview) +- [02. Data Overview & Preparation](#data-overview) +- [03. Applying Causal Impact Analysis](#causal-impact) +- [04. Analyzing The Results](#analyzing_results) +- [05. Discussion](#discussion) + +___ + +# Project Overview +
+ +### Context + +In late June, a grocery retailer promoted their new “Delivery Club” membership campaign. For a $100 annual fee, members receive unlimited free grocery deliveries for an entire year, starting July 1st. Having previously evaluated the impact of campaign mailers on signup rates using a [Chi-Square Test of Independence](https://golden-hail.github.io/2026/06/30/Chi-Square-Test.html), leadership now needs to quantify the financial impact of the program. Specifically, the client wants to determine whether customers who signed up for the Delivery Club increased their spending in the months following the launch—and by how much. + +**Our core business hypothesis is that waiving delivery fees removes purchasing friction, encouraging Delivery Club members to shop more frequently and spend more per order over time.** To measure this, we leverage a counterfactual framework using non-member spending behavior. Customers who chose not to sign up *should* in theory continue their normal shopping habits after July 1st. By calculating average daily sales for this non-member control group, we can establish a reliable baseline prediction of what Delivery Club members *would have spent* if the program had never existed. + +Ultimately, our primary objective is to evaluate average daily spend across both customer cohorts from July 1 through September 30. Comparing actual member transactions against this synthetic baseline allows us to differentiate true campaign uplift from organic market growth and quantify the net incremental revenue directly caused by the membership launch. + +
+ +### Actions + +* **Environment & Data Setup:** Import required analytical libraries (`causalimpact`, `pandas`). Load raw transaction and campaign datasets. +* **Data Integration & Aggregation:** Merge customer transactions with campaign metadata on `customer_id` and aggregate daily spend into a unified customer time-series DataFrame. +* **Format Model Matrix:** Pivot data to generate mean daily spend by group (`signup_flag`), explicitly ordering the treatment group (Delivery Club members) into the first column and non-members into the second. +* **Define Evaluation Windows:** Set explicit date boundaries for the pre-intervention baseline period (`2020-04-01` to `2020-06-30`) and post-intervention assessment window (`2020-07-01` to `2020-09-30`). +* **Model Execution:** Run the `CausalImpact` algorithm to generate a counterfactual baseline and calculate 95% Bayesian confidence intervals. +* **Performance Evaluation:** + * Utilize `ci.plot()` to visually assess daily spending trajectory, cumulative pointwise causal effects, and spend lift over time. + * Run `ci.summary()` to extract numerical estimates for average daily lift, percentage increase, and total incremental revenue generated. + +
+ +### Results & Discussion + +The Delivery Club campaign successfully generated a statistically significant increase in customer spending over the 92-day evaluation window, fully confirming our core business hypothesis. + +* **Relative & Absolute Lift:** Campaign members spent an average of *$171.33 per day*, compared to an estimated counterfactual baseline of *$121.42 per day*. This represents an absolute gain of **+$49.92 per customer per day**, and **+41.11% relative uplift** in daily spend. +* **Top-Line Revenue Contribution:** Across all active members over the post-launch window, the program drove **$4,592.49 in net incremental revenue** ($15,762.69 actual spend vs. $11,170.20 predicted baseline). +* **Statistical Certainty:** The model confirms a `p-value` of `p = 0.0` (`100% posterior causal probability`), confirming with high statistical confidence that the sales uplift was directly caused by the Delivery Club launch rather than random noise or natural circumstances. The 95% Credible Interval bounds the **true relative lift between +34.33% and +48.10%**. + +___ + +# Concept Overview + +Causal Impact Analysis is a statistical method built by researchers at Google in 2014. It predicts what *would have happened* if a treatment event (known as an intervention) never took place and compares that prediction to what actually happened with the observed data. + +![alt text](/img/posts/Causal_Impact_Example.jpg) + +In the example plot image above, the following key components are represented: + +* **Observed (Red Line):** This curve represents the true recorded metric over time, both before and after the "Change Made" treatment event. This is the treatment group. +* **Change Made (Vertical Line at t = 10):** Represents the exact timestamp at which the treatment event took place. This datapoint separates the **pre-period** and **post-period**. +* **Pre-period (t < 10):** The period of time before the treatment event. This timeframe is used to train the model on the *relationship between target and control variables.* +* **Post-period (t > 10):** The period of time after the treatment event where the causal effect is measured. +* **Counterfactual Estimate (Blue Line):** This curve represents an estimated prediction of what *would have happened* if the treatment event never occurred. It is created by feeding the CausalImpact algorithm control metrics that are not affected by the event. +* **Causal Effect (Purple Vertical Arrow):** The delta between the **Observed** and **Counterfactual Estimate** curves, representing the absolute lift. + +
+ +### Model Setup Requirements + +To run the analysis via the Python `causalimpact` package, input data must be structured as follows: +* **Time Index:** A DataFrame indexed by datetime. +* **Target Variable (Column 1):** The response metric being evaluated (example: daily spend for the treatment group). +* **Control Variables (Columns 2+):** One or more time-series metrics unaffected by the **intervention** used by the model to reconstruct the counterfactual baseline. + +***Performance Note for Large Datasets (`tfcausalimpact`):** For multi-year or high-frequency datasets, the standard Python package can run slowly. Installing `tfcausalimpact` optimizes execution by translating your DataFrame into TensorFlow Probability structures (`tfp.sts`) to drastically accelerate Bayesian computations and avoid deprecation warnings.* + +
+ +### Why Use Causal Impact Analysis? + +Causal Impact Analysis automatically accounts for trend shifts, seasonality (e.g., weekends vs. weekdays), and historical patterns using Bayesian structural time-series models. It also quantifies uncertainty by outputting probabilistic confidence intervals alongside point estimates to make risk and impact clearly measurable. + +___ + +# Data Overview & Preparation + +First, import the required packages for data processing and causal impact analysis: + +```python +from causalimpact import CausalImpact +import pandas as pd +``` + +Next, we'll import and merge our data tables of interest: + +* The *transactions* table contains individual customer transactions, with fields such as `customer_id`, `transaction_date`, `transaction_id`, `num_items`, and `sales_cost` + +* The *campaign_data* table contains data from the Delivery Club campaign, tracking which type of mailer each customer received (`mailer_type`) and whether they signed up or not (`signup_flag`). + +```python +# Import data tables +transactions = pd.read_excel('data/grocery_database.xlsx', sheet_name = 'transactions') +campaign_data = pd.read_excel('data/grocery_database.xlsx', sheet_name = 'campaign_data') +``` + +Because the *transactions* table tracks data from April through September, daily sales serves as the appropriate time-series metric. We group the data by `customer_id` and `transaction_date` to aggregate individual daily spending into a new DataFrame: `customer_daily_sales`. Retaining `customer_id` at this stage allows us to successfully merge the aggregated sales numbers with our *campaign_data* table. + +```python +# Aggregate sales cost per customer per day +customer_daily_sales = transactions.groupby(['customer_id', 'transaction_date'])['sales_cost'].sum().reset_index() + +# Merge data tables on customer_id +customer_daily_sales = pd.merge(customer_daily_sales, campaign_data, how = 'inner', on = 'customer_id') +``` + +
+Below is a 5-row sample of the imported `customer_daily_sales` DataFrame: +
+ +| **customer_id** | **transaction_date** | **sales_cost** | **campaign_name** | **campaign_date** | **mailer_type** | **signup_flag** | +|---|---|---|---|---|---|---| +| 4 | 2020-04-01 00:00:00 | 193.96 | delivery_club | 2020-07-01 00:00:00 | Mailer1 | 1 | +| 5 | 2020-04-01 00:00:00 | 755.77 | delivery_club | 2020-07-01 00:00:00 | Mailer2 | 1 | +| 22 | 2020-04-01 00:00:00 | 21.83 | delivery_club | 2020-07-01 00:00:00 | Control | 0 | +| 37 | 2020-04-01 00:00:00 | 53.72 | delivery_club | 2020-07-01 00:00:00 | Mailer1 | 0 | +| 42 | 2020-04-01 00:00:00 | 319.36 | delivery_club | 2020-07-01 00:00:00 | Mailer1 | 1 | + +To prepare the dataset for the `CausalImpact` algorithm, the *customer_daily_sales* DataFrame must meet the following requirements: +1. **Datetime Index:** The rows must be uniquely indexed by a continuous datetime sequence. +2. **Column Ordering:** The **first column** must represent the Target Variable (treatment group metric), followed by one or more **Control Columns** (unaffected baseline series). + +To achieve this, we can first pivot `customer_daily_sales` into a new DataFrame to aggregate average daily spend across both customer groups: members who signed up for the Delivery Club (`signup_flag = 1`, treatment) and members who did not (`signup_flag = 0`, control). + +```python +causal_impact_df = customer_daily_sales.pivot_table(index = 'transaction_date', + columns = 'signup_flag', + values = 'sales_cost', + aggfunc = 'mean').round(2) + +# Define frequency for our DateTimeIndex ("D" = daily) +causal_impact_df.index.freq = "D" +``` + +| **transaction_date** | 0 | 1 | +|---|---|---| +| 2020-04-01 | 74.46 | 194.49 | +| 2020-04-02 | 75.56 | 185.16 | +| 2020-04-03 | 74.39 | 118.12 | +| 2020-04-04 | 63.00 | 198.53 | +| 2020-04-05 | 72.44 | 145.46 | + +Now that `causal_impact_df` is indexed by unique `transaction_date`, the two columns must be rearranged so that the impacted treatment group data occupies the first column position. + +```python +# For causal impact we need the impacted group in the first column (see required columns) +causal_impact_df = causal_impact_df[[1,0]] + +# Rename columns for clarity +causal_impact_df.columns = ["member", "non_member"] +``` + +Now, the `causal_impact_df` input data is correctly formatted and ready for model fitting: + +| **transaction_date** | member | non_member | +|---|---|---| +| 2020-04-01 | 194.49 | 74.46 | +| 2020-04-02 | 185.16 | 75.56 | +| 2020-04-03 | 118.12 | 74.39 | +| 2020-04-04 | 198.53 | 63.00 | +| 2020-04-05 | 145.46 | 72.44 | + +___ + +# Applying Causal Impact Analysis + +Before running the `CausalImpact` algorithm, the *pre_period* and *post_period* must be defined in addition to the `causal_impact_df` input. + +The `pre_period` represents the baseline timeframe prior to the Grocery Club campaign being launched. The `post_period` spans the timeframe immediately following the campaign launch. + +*Note on dataset: Although the campaign memberships last for a year, the available transactions data ends on 2020-09-30, defining the limit of the `post_period` evaluation window.* + +```python +# Time period before the Delivery Club campaign +pre_period = ["2020-04-01","2020-06-30"] +# Evaluation window after campaign launch +post_period = ["2020-07-01","2020-09-30"] + +# Fit the Causal Impact model +ci = CausalImpact(causal_impact_df, pre_period, post_period) +``` + +Running this code fits the CausalImpact model and stores the statistical output inside the `ci` object. To evaluate the campaign’s performance, the following core methods will be used: `.plot()` to visualize the counterfactual trajectory and `.summary()` to quantify the absolute lift. + +___ + +# Analyzing The Results + +Executing `ci.plot()` generates a three-panel visualization. Across all three subplots, the **vertical dashed black line** denotes the intervention date (`2020-07-01`), separating the model's training window (`pre-period`) from the assessment window (`post-period`). + +```python +ci.plot() +``` + +![alt text](/img/posts/ci_plot.jpg) + +1. **Original vs. Counterfactual (First subplot):** +* **Observed Data (`'y'`, Solid Black Line)**: Shows the true recorded average daily sales for customers who joined the Delivery Club. +* **Counterfactual Prediction (`'Predicted'`, Blue Dashed Line)**: Represents the estimated average daily spend if those customers had never joined the campaign. +* **Confidence Interval (Shaded Purple Region):** Illustrates the 95% posterior probability interval around the prediction, which is the `CausalImpact` default. +* **Takeaway:** Post-intervention, observed daily spend (``'y'``) consistently trends above the baseline prediction, indicating a clear positive lift from the membership launch. Because the actual `post-period` spend consistently breaches the upper boundary of the purple band, we can conclude that the observed uplift in sales is statistically significant and extremely unlikely to be due to random noise. + +2. **Point Effects (Second subplot):** +* Displays the daily delta between actual observations and the counterfactual baseline. +* In the `pre-period`, point effects hover around zero as expected, verifying strong baseline alignment. +* In the `post-period`, daily point effects jump into positive value territory, demonstrating that daily sales consistently surpassed expected baselines. + +3. **Cumulative Effect (Third subplot):** +* Aggregates daily pointwise uplift over time into a running cumulative sum. +* The steady upward slope throughout July, August, and September confirms that the campaign generated compounding incremental revenue, leading to several thousand dollars of total lift by the end of the evaluation window. + +From a first look at the `ci.plot`, it seems as though customers who signed up for the campaign ended up spending more daily indicating a successful campaign! + +We can further support our statistical prediction by running the `ci.summary()` functionality; this will help quantify the exact magnitude and statistical significance of the plotted lift: + +```python +# Extract the summary statistics & report +print(ci.summary()) + +>> Posterior Inference {Causal Impact} + Average Cumulative +Actual 171.33 15762.69 +Prediction (s.d.) 121.42 (4.26) 11170.2 (392.29) +95% CI [112.93, 129.65] [10389.73, 11927.46] + +Absolute effect (s.d.) 49.92 (4.26) 4592.49 (392.29) +95% CI [41.69, 58.4] [3835.23, 5372.96] + +Relative effect (s.d.) 41.11% (3.51%) 41.11% (3.51%) +95% CI [34.33%, 48.1%] [34.33%, 48.1%] + +Posterior tail-area probability p: 0.0 +Posterior prob. of a causal effect: 100.0% + +For more details run the command: print(impact.summary('report')) +``` + +This output summary table breaks down the evaluation window into two key dimensions: `Average` (daily per-customer metrics) and `Cumulative` (total aggregate metrics over the post-period) + +**Average Daily Impact**: +* *Actual vs. Predicted*: Delivery Club members spent an average of $171.33 per day during the post-period, compared to a predicted counterfactual baseline of $121.42. +* *Absolute Effect:* The campaign drove an incremental daily lift of $49.92 per member ($171.33 - $121.42). +* *Relative Effect:* This daily lift represents a 41.11% proportional increase in average daily spending over baseline expectations. + +**Cumulative Total Impact:** +* Across the entire 92-day post-period evaluation window, total actual spend for all campaign members reached **$15,762.69**, compared to the expected counterfactual total of **$11,170.20**. +* This yields a **total net incremental revenue** generated by the campaign of **$4,592.49**. + +**Statistical Significance & Uncertainty:** +* **Confidence Intervals:** The model establishes a 95% Credible Interval bounding the true relative lift between **+34.33% and +48.10%**. Reporting these bounds gives stakeholders a clear safety margin, confirming that even under the most conservative scenario, the campaign delivered at least a 34% sales boost. +* **P-Value & Causal Certainty:** The model reports a **`p-value` of `p = 0.0`** (**`100% causal probability`**). This confirms with extreme statistical certainty that the revenue jump was directly caused by the Delivery Club launch, rather than random noise or normal shopping fluctuations. + +___ + +# Discussion + +### Business Impact & Strategic Implications + +The true value of Causal Impact Analysis lies in isolating real incremental gains from baseline sales trends. Standard transactional reporting would credit the entire $15,762.69 post-period spend to the Delivery Club. However, by constructing a synthetic counterfactual ($11,170.20), we isolated the true top-line contribution of the initiative to **$4,592.49 in net incremental revenue**. + +Waiving delivery fees effectively removed purchasing friction for active members, driving an absolute gain of **+$49.92 per customer per day** through higher transaction frequencies and larger order values across the 92-day evaluation window. + +From a financial planning perspective, this **$4,592.49 net revenue lift** serves as the exact top-line figure leadership needs for ROI modeling. By comparing this revenue gain directly against campaign execution costs—such as promotional mailer printing, delivery fee subsidies, and operational logistics—the business can measure exact campaign profitability and determine long-term program sustainability. + +### Next Steps + +* **Financial Planning & ROI Modeling:** This **$4,592.49 net revenue lift** provides leadership with the precise top-line baseline required to evaluate overall campaign ROI against marketing costs, delivery fees, and operational expenses. +* **Customer Lifetime Value (LTV) Tracking:** Extend the post-intervention evaluation window as additional transaction history becomes available to assess retention rates and determine whether daily spending lift persists throughout the full membership year. +* **Custom Plot Formatting:** Enhance default `ci.plot()` outputs using custom `matplotlib.pyplot` styling to generate stakeholder-ready visualizations with clear color palettes, axes labels, and explicit legend labels. diff --git a/_site/2026/05/15/Prime-Numbers-Finder.html b/_site/2026/05/15/Prime-Numbers-Finder.html new file mode 100644 index 000000000..8c681d55e --- /dev/null +++ b/_site/2026/05/15/Prime-Numbers-Finder.html @@ -0,0 +1,304 @@ + + + + + + + Prime Numbers Finder with Python + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Prime Numbers Finder with Python

+ +
+
+

Hello! In this post, I’ll be walking through a Python function I wrote that returns a list of all prime numbers that are below a specified input number, n. For example, if we passed in a value n=10, this function would return a list of all prime numbers below the specified number 10!

+ +

For reference, a prime number is an integer that can only be divided wholly by itself and 1. In our example of n=10, we would expect an output as follows:

+
>>> {2, 3, 5, 7}
+
+ +
+

Before we jump into the final script, we’ll be walking through each functional step of the code. In the end, we’ll have a while loop that loops through a range of numbers up to the defined number, n that is able to return a list of prime numbers within that range.

+ +

First, let’s set our input value n, the number in which we want to find all prime numbers up to.

+ +
n = 20
+
+ +

Next, let’s define the range of values, up to n = 20.

+ +

The lowest prime number to exist is 2. Because of this, we want our initial list of possible prime numbers, number_range, to be a list of numbers starting from 2 and ending at specified input “n”.

+ +

Instead of an ordinary list, however, we’ll be using a set for our number_range. Sets are a good choice as sets are faster than lists and contain unique values, however, Stay tuned to find out why!

+ +
number_range = set(range(2, n+1))
+>>> {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+
+

The end goal is to loop through this number_range and extract each value from this set with the pop method to determine whether each value is a prime number. If the routine determines a value is prime, we’ll append it onto the new primes_list list. Let’s initialize that empty list!

+ +
primes_list = []
+
+ +

The pop method removes an element from a list or set, and returns that value to us. 2 is the first value in our number_range, and is the lowest prime number to exist. Let’s pop it out and assign it to prime

+ +
prime = number_range.pop()
+print(prime)
+>>> 2
+print(number_range)
+>>> {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+
+ +

After the pop is applied, value 2 is no longer in our number_range set. We’ll then add this first prime number prime to our empty primes_list

+ +
primes_list.append(prime)
+print(primes_list)
+>>> [2]
+
+ +

All multiples of 2 are by default not prime numbers, since they can be divided by 2 in addition to itself and 1. Let’s define a new set for all multiples of 2 that are lower than the defined n=20 value.

+ +
multiples = set(range(prime*2, n+1, prime))
+
+ +

The range was determined as follows: range(start, stop, step)

+ +

For the START in our range, we can start with prime*2, which will be the first multiple of the prime number.
+For the STOP in our range, we want to stop at the index of our number n=20, which is the value of n+1.
+For the STEP in our range, we want multiples of our prime, so we want to increment in steps of this popped out prime number so we can put in our prime list.

+ +

Now we can see that this range worked in our output as we have all multiples of 2 up to our n value of 20!

+ +
print(multiples)
+>>> {4, 6, 8, 10, 12, 14, 16, 18, 20}
+
+ +

Here’s where the magic happens! Why did we use sets? With sets, we can use a special functionality called difference_update.

+ +

Before we apply the difference_update method, let’s look at our two sets again.

+ +
print(number_range)
+>>> {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
+
+print(multiples)
+>>> {4, 6, 8, 10, 12, 14, 16, 18, 20}
+
+ +

difference_update will be used to update our number_range by dropping the values that exist in the multiples list we defined.

+ +
number_range.difference_update(multiples)
+print(number_range)
+>>> {3, 5, 7, 9, 11, 13, 15, 17, 19}
+
+ +

Our number_range list is now trimmed down to not include multiples of 2, as we’ve determined the multiples values are not prime. This vastly reduces how many numbers in the number_range we have left to assess. The same steps above can now be applied to the the first number in the new number_range set, 3. We would pop 3 into the primes_list, find all multiples of 3 up to value n=20, and then difference_update to remove the non-prime multiples from our number_range set.

+ +

We now have all the pieces to make this loop work, where we can define n and have the script return the primes_list on it’s own!

+ +

Let’s assemble our while loop and up the ante, setting our n value to 1000:

+ +
n = 1000
+
+# define number range
+number_range = set(range(2, n+1))
+
+# create empty list to append prime numbers to
+primes_list = []
+
+# iterate until the number_range set is empty
+while number_range:
+    prime = number_range.pop()
+    primes_list.append(prime)
+    multiples = set(range(prime*2, n+1, prime))
+    number_range.difference_update(multiples)
+	
+print(primes_list)
+>>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
+
+ +

Success! Now let’s throw this all into a function.

+ +

A list of the prime numbers was our goal, however, we can also return a few more details to the user about the extracted primes_list. We can print the amount of prime numbers found in our range as well as what the highest prime number is in the set.

+ +
def primes_finder(n):
+    
+    # define number range
+    number_range = set(range(2, n+1))
+
+    # create empty list to append prime numbers to
+    primes_list = []
+
+    # iterate until the number_range set is empty
+    while number_range:
+        prime = number_range.pop()
+        primes_list.append(prime)
+        multiples = set(range(prime*2, n+1, prime))
+        number_range.difference_update(multiples)
+    
+	# count how many primes in primes_list output
+    prime_count = len(primes_list)
+	# find the largest prime number
+    largest_prime = max(primes_list)
+	# print the details
+	print(f"There are {prime_count} prime numbers between 1 and {n}, the largest of which is {largest_prime}")
+	
+	>>> There are 168 prime numbers between 1 and 1000, the largest of which is 997
+
+ +

With our function now defined, it’s as easy as running primes_finder(n) where n can be any numerical value of choice.

+ +
primes_finder(1000000)
+>>> There are 78498 prime numbers between 1 and 1000000, the largest of which is 999983
+
+ +

And there we have it! Feel free to copy the function and try it for yourself! :)

+ +

A NOTE ON POP():

+ +

In this script, we demonstrated the pop functionality, however, using pop() on a set could be risky in some instances.

+ +

Pop() extracts the lowest value of a list or set, which seems confusing since sets are unordered by definition. Under the Python covers, however, the elements in the set are internally stored with an order determined by the hash code of each element. Occassionally, this hash may provide a value that is not the lowest.

+ +

To safeguard against this, we can replace the definition of of prime value to from prime = number_range.pop() to..

+ +
prime = min(sorted(number_range))
+number_range.remove(prime)
+
+ +

This new definition of prime ascertains that we are always pulling the lowest value out of our number_range. This method, however, is slower as we are sorting the number_range set each time we define prime.

+ + + + +
+ +
+ + + + + + +
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/_site/2026/06/12/Earthquake-Tracker-Tableau.html b/_site/2026/06/12/Earthquake-Tracker-Tableau.html new file mode 100644 index 000000000..3c117f4ae --- /dev/null +++ b/_site/2026/06/12/Earthquake-Tracker-Tableau.html @@ -0,0 +1,219 @@ + + + + + + + Earthquake Tracker - Tableau Dashboard + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Earthquake Tracker - Tableau Dashboard

+ +
+
+

This interactive Tableau dashboard tracks global earthquake activity for a given data set of a 30-day period.

+ +

NOTE: Due to formatting troubles between Tableau, Ruby, and git-pages, it is recommended to view the dashboard on Tableau Public itself by clicking “View on Tableau Public” on the bottom left of the dashboard below.

+ +
    +
  • Take in the overall data summary for earthquakes over a 30-day period.
  • +
  • See the frequency of earthquakes in each location as well as the average and maximum magnitude
  • +
  • For finer detail, scroll for a summary of particular days in the upper right hand slide bar
  • +
  • Hover your mouse over the data points on the map to easily see earthquake information, such as locations and magnitudes for each recorded event
  • +
+ +
+ + +
+ + +
+ + + +
+ +

Data Disclaimer:

+

This earthquake data in this dashboard is not realistic whatsoever. The frequency of high magnitude quakes are drastically exaggerated to provide diverse datapoints to demonstrate data visualization in Tableau. If we were to receive legitimate data from an agency like the USGS, we’d be able to simply upload the real world data into our proof-of-concept dashboard

+ +

Fun Fact(s):

+
    +
  • The greatest magnitude of an earthquake ever recorded was in Chili in 1960 at a magnitude of 9.5.
  • +
  • Earthquakes cannot exceed a magnitude of 10 because no tectonic fault line is long enough to produce one. A magnitude 10 earthquake would require a fault wrapping all the way around the equator
  • +
  • The USGS (United States Geological Survey) estimates an average of 16 major earthquakes per year, with a magnitude of 7.0 or higher
  • +
+ + + + +
+ +
+ + + + + + +
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/_site/2026/06/30/Chi-Square-Test.html b/_site/2026/06/30/Chi-Square-Test.html new file mode 100644 index 000000000..0138d92c5 --- /dev/null +++ b/_site/2026/06/30/Chi-Square-Test.html @@ -0,0 +1,540 @@ + + + + + + + Assessing Campaign Performance Using Chi-Square Test For Independence + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Assessing Campaign Performance Using Chi-Square Test For Independence

+ +
+
+

In this project, we’ll be running an A/B test on grocery retailer campaign data to determine if the quality of the promotion mail sent to customers significantly impacted signup rates for a promoted “Delivery Club” membership.

+ +

We will do this through applying the Chi-Square Test of Independence to measure the significance of the difference in signups between 2 groups of customers.

+ +

Table of contents

+ + + +
+ +

Project Overview

+ +


+

Context

+ +

In June, a grocery retailer client ran a campaign to promote their new “Delivery Club” memberships. Signing up for the club costs $100 and gives customers free grocery deliveries for one year, starting July 1st.

+ +

For the campaign promoting the club, customers were put randomly into three groups:

+
    +
  • The first group received a bland, low cost mailer, Mailer 1.
  • +
  • The second group received a colorful, high quality, high cost mailer, Mailer 2.
  • +
  • The third group was a control group. They did not receive any mail.
  • +
+ +

The client knows that customers who were contacted, signed up for the Delivery Club at a far higher rate than the control group, but are now curious as to if there is a significant difference in customer signup rate between the cheap mailer and the expensive mailer. This will allow them to make more informed decisions in the future, such as whether it is worth it to spend the money on higher cost mailers or not.

+ +


+ +

Actions

+ +

The Chi-Square Test of Independence will be applied to compare the signup rates of two distinct groups from our campaign_data client database:

+
    +
  • Customers who received low cost Mailer1
  • +
  • Customers who received the higher quality, high cost Mailer2.
  • +
+ +

The Hypothesis Test elements are defined as:

+
    +
  • Null Hypothesis: There is no relationship between mailer type and signup rate. They are independent variables and any difference in signup rates are due to chance.
  • +
  • Alternate Hypothesis: There is a true relationship between mailer type and signup rate. They are not independent.
  • +
  • Significance Level: 0.05 (5%)
  • +
+ +

Through Pandas, we can aggregate the observed data of mailer_type and signup_flag into a 2x2 matrix. We’ll then feed these observed frequencies to the chi2_contingency algorithm provided by the scipy library to calculate the expected values, p-values, Degrees of Freedom (dof), and the Chi-Square Statistic.

+ +

We’ll then take this one step further and find our Critical Value to truly get a grasp on whether we should reject or fail to reject our null hypothesis.

+ +

See more details on the Chi-Square Test, and other related concepts in the *Concept Overview section*

+ +

Note: Another viable choice for an approach comparing rates would be Z-Test For Proportions, which would provide us with the same statistical result as the Chi-Square Test would. However, in this business case, it was determined to be more beneficial to use the Chi-Square Test for analysis.

+ +

The Chi-Square Test can be represented using a 2x2 data matrix, making the data easier to visualize and explain to stakeholders. Additionally, if the company ever had interest in expanding this campaign to more than two groups, we could easily adapt the Chi-Squared approach to include new group data, providing the business with a consistent way to measure variable significance.

+ +


+ +

Results & Discussion

+ +

At a first glance of the observed data, it appears that the high-cost mailer yielded a higher signup rate:

+ +
* Mailer 1 (low-cost): 32.8% signup rate
+* Mailer 2 (high-cost): 37.8% signup rate
+
+ +

However, the results of the Chi-Square Test of Independence indicate otherwise:

+ +
* Chi-Square Statistic: 1.72 < Critical Value: 3.84
+* p-value: 0.19 > Acceptance Criteria 0.05
+
+ +

With these results, we retain the null hypothesis and conclude there is no measurable relationship between mailer cost and signup rates. This suggests that the 5% variation in signup rate could be due to random chance.

+ +

Business Impact:
+Upgrading to the higher-cost mailer risks increasing spending without reliably driving additional revenue.

+ +

Next Steps:
+As current findings do not support a permanent shift to higher-cost mailers, we advise running additional A/B tests with larger sample sizes to confirm whether a true performance gap exists or not.

+ +
+ +

Concept Overview

+


+

A/B Testing

+ +

An A/B test takes two randomized groups, A and B, and provides them with different experiences. In the A/B test, we measure the response of both groups to understand the impact each experience had on the response.

+ +

For example, a company may post 2 different pictures advertising the same product on their website. With an A/B test, we could look to measure if the picture used in the ad significantly impacted the number of users who clicked on the ad. If one ad yielded significantly more clicks, the business can use this data when thinking about what characteristics got the user to click and incorporate those features into future ads.

+ +


+

Hypothesis Testing

+ +

A Hypothesis Test is a statistical method used to evaluate the likelihood of an assumption on a population, using sample data. It determines whether an observed pattern or correlation in the data is due to a true relationship or random chance.

+ +

There are multiple types of Hypothesis Tests as well as many scenarios we can run them on.

+ +

When performing any Hypothesis Test, the following must always be defined:

+ +
    +
  • +

    The Null Hypothesis

    + +

    The “Null Hypothesis” is a statistical assumption stating that there is no statistically significant relationship, association, or difference between two outcomes or groups. We run a Hypothesis Test to either reject or support this Null Hypothesis.

    +
  • +
  • +

    The Alternate Hypothesis

    + +

    The “Alternate Hypothesis” suggests that there is a significant and measurable relationship, effect, or difference between variables, directly contradicting the Null Hypothesis. When we reject the Null Hypothesis, we accept the Alternate Hypothesis, concluding that the observed relationship is highly unlikely to have occurred by chance alone.

    +
  • +
  • +

    The Significance Level

    + +

    To statistically determine whether to reject the null hypothesis in favor of the alternate, a “significance level” must be specified. The significance level is a specified p-value threshold in which we are measuring our null hypothesis against. In other words, the set threshold draws a line between what we consider random chance and what we consider a statistically significant result.

    + +

    A p-value, or probability value, is a calculated value used to determine if the data is extreme enough to reject the null hypothesis. It is a common practice to set the significance level to 0.05 or 5%.

    + +
      +
    • +

      A low p-value (≤ 0.05) suggests your results are highly unlikely to have occurred by chance. There is strong evidence to reject the null hypothesis as the relationship is statistically significant

      +
    • +
    • +

      A High p-value (> 0.05) means your results could easily happen under random variation. We would fail to reject the null hypothesis, meaning there isn’t enough evidence to prove a true association exists

      +
    • +
    +
  • +
+ +


+

Chi-Square Test of Independence

+ +

The Chi-Square Test of Independence is a hypothesis test used to determine whether a significant association exists between two categorical variables. It compares the observed frequencies from the actual data points collected from a sample against the expected frequencies, the data expected to be seen if the two variables were truly independent.

+ +

The Null Hypothesis described above is our baseline assumption. It assumes that there is no relationship or difference between the two variables. It asserts that the observed frequencies of data will match the expected frequencies, with any minor difference being the result of random chance.

+ +

The Chi-Square Contingency function provides a Chi-Square statistic that can be compared against a calculated critical value, in order to reject or fail to reject a null hypothesis.

+ +
    +
  • +

    Chi-Square Statistic < Critical Value: We retain the null hypothesis - the observed results could easily happen under random variation, meaning there isn’t enough evidence to prove a true association exists between variables

    +
  • +
  • +

    Chi-Square Statistic ≥ Critical Value: We reject the null hypothesis - the observed results are highly unlikely to have occurred by chance providing strong evidence that an association exists between variables

    +
  • +
+ +

This function also provides a p-value, which can be compared to the chosen significance level (commonly set to 0.05). If the p-value is less than or equal to this significance level, the null hypothesis is rejected.

+ +
+ +

Data Overview & Preparation

+ +

Our table of interest in the grocery client database is the campaign_data table. +This table contains each unique customer_id, the type of mailer they received, if any, and whether or not the customer signed up for the Delivery Club membership.

+ +

To determine whether the fancier Mailer 2 led to a significant difference of people to sign up as opposed to the cheaper Mailer 1, we will first need to exclude the control group from the data by extracting the customers who got either mailer.

+ +
# import the required python libraries
+import pandas as pd
+from scipy.stats import chi2_contingency, chi2
+
+# import campaign data
+campaign_data = pd.read_excel(...)
+
+# filter out the control group
+campaign_data = campaign_data.loc[campaign_data["mailer_type"] != "Control"]
+
+


+Below is a 10 row sample of the imported campaign_data DataFrame: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
customer_idcampaign_namemailer_typesignup_flag
74delivery_clubMailer11
524delivery_clubMailer11
607delivery_clubMailer21
343delivery_clubMailer10
322delivery_clubMailer21
115delivery_clubMailer20
1delivery_clubMailer21
120delivery_clubMailer11
52delivery_clubMailer11
405delivery_clubMailer10
435delivery_clubMailer20
+ +

In the campaign_data DataFrame we have the following columns:

+ +
    +
  • customer_id
  • +
  • campaign name
  • +
  • mailer_type (either Mailer1 or Mailer2)
  • +
  • signup_flag (either 1 or 0)
  • +
+ +
+ +

Applying Chi-Square Test of Independence

+ +


+ +

State Hypotheses & Significance Level For Test

+ +

To kick off our Hypothesis Test, we’ll need to define our Null Hypothesis, our Alternate Hypothesis, and our Significance Level. (See more on these terms in the Concept Overview section above)

+ +

For our significance level, we’ll be using the commonly used value of 0.05 (or 5%).

+ +
    +
  • null_hypothesis: There is no relationship between mailer type and signup rate. They are independent.
  • +
  • alternate_hypothesis: There is a relationship between mailer type and signup rate. They are not independent.
  • +
  • significance_level: 0.05
  • +
+ +


+

Calculate Observed Frequencies & Expected Frequencies

+ +

As detailed in the Concept Overview section above, the observed frequencies come directly from the rates per group in our collected data. To get these frequencies, we’ll create our 2x2 matrix needed for the Chi-Square approach, using a method called crosstab().

+ +

Our observed values come directly from our campaign_data imported above. We are analyzing the impact that mailer_type had on member signup rates, so we’ll want to pass these data points into the crosstab method.

+ +

We can visualize this data in the DataFrame below:

+
observed_vals = pd.crosstab(campaign_data['mailer_type'],campaign_data['signup_flag'])
+
+print(observed_vals)
+>>> signup_flag    0    1
+>>> mailer_type          
+>>> Mailer1      252  123
+>>> Mailer2      209  127
+
+ +

By running the crosstab method, we see:

+
    +
  • For customers who received Mailer 1, +
      +
    • 252 customers did not sign up for the promotion
    • +
    • 123 customers signed up for the promotion.
    • +
    +
  • +
  • For customers who received Mailer 2, +
      +
    • 209 customers did not sign up for the promotion
    • +
    • 127 customers signed up for the promotion
    • +
    +
  • +
+ +

The Chi-Squared contingency function won’t accept Pandas DataFrames, so we’ll have to convert the observed values data into an array by using .values property for observed_values.

+ +
observed_values = pd.crosstab(campaign_data['mailer_type'],campaign_data['signup_flag']).values
+print(observed_values)
+>>> array([[252, 123],
+       [209, 127]])
+
+ +

Now that the observed_values are in an array, we can pass the observed_values into our chi2_contingency function that we imported with scipy.

+ +

Running the Chi-Squared function will provide us with the following:

+
    +
  • Expected values
  • +
  • P-values
  • +
  • Degrees of Freedom (dof): used for finding the critical value later
  • +
  • Chi2 Statistic
  • +
+ +

We can additionally test the null hypothesis by finding the critical value along our Chi-Squared distribution based on our set significance level and our calculated dof, using the chi2.ppf percentage point function from the scipy library.

+ +
# run the chi-square test
+chi2_statistic, p_value, dof, expected_values = chi2_contingency(observed_values, correction = True)
+
+print(chi2_statistic)
+>> 1.72
+
+print(p_value)
+>> 0.19
+
+# find the critical value for our test using chi2.ppf
+critical_value = chi2.ppf(1 - significance level, dof)
+
+print(critical_value)
+>> 3.84
+
+ +

Note: The chi2_contingency function accepts a correction parameter. By setting correction = True, we are applying Yate’s Correction, which is applicable when comparing 2 group totals against their results in a 2x2 matrix

+ +

With that code in place, we have all of the values necessary to analyze the results for our A/B test and draw a conclusion.

+ +
+ +

Analyzing The Results

+ +

Based upon the raw observed values from the campaign, we can see that the signup rate for customers receiving Mailer 2 was higher than for the customers receiving Mailer 1:

+ +
    +
  • Mailer 1 (low-cost): 32.8% signup rate
  • +
  • Mailer 2 (high-cost): 37.8% signup rate
  • +
+ +

The Chi-Square test gives us further insight into whether this difference in signup rate was truly due to the quality of the mailer, or just due to chance.

+ +

Our calculated p-value of 0.19 is greater than our set significance level of 0.05, meaning that the difference in signup outcomes between the two mailing groups is not significant.

+ +

This conclusion is further supported by our Chi-Square statistic of 1.72 being lower than the calculated critical value of 3.84. In both cases, we retain the null hypothesis as there is not enough evidence that the signup rates for Mailer 1 and Mailer 2 were significantly different.

+ +
+ +

Discussion

+ +

While Mailer 2 yielded a higher signup rate than Mailer 1 (37.8% vs. 32.8%), the difference was not statistically significant at our 0.05 significance level. The results of the Chi-Square test of independence reveal that we cannot yet conclude whether the more expensive mailer had a true impact on whether a customer signed up.

+ +

At first glance, the client might look at the raw percentages and assume the premium quality of Mailer 2 was the driving force behind the higher signup rate. Without this hypothesis test, they may have considered exclusively sending the more expensive mailers for their next promotion, potentially wasting valuable funding.

+ +

It is important to note that this analysis was limited to a small sample size from a single campaign. Failing to reject the null hypothesis does not definitively prove that mailer quality didn’t impact signup rates, it simply means we currently lack the evidence to prove that it does. Therefore, we advise the client not to jump to conclusions just yet. Running additional A/B tests over time will provide deeper insights. If future data consistently supports retaining the null hypothesis, we can then confidently recommend sticking to the cheaper mailers to optimize printing costs.

+ + + + +
+ +
+ + + + + + +
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/_site/2026/08/01/E-Commerce-AB-Test.html b/_site/2026/08/01/E-Commerce-AB-Test.html new file mode 100644 index 000000000..8add61c51 --- /dev/null +++ b/_site/2026/08/01/E-Commerce-AB-Test.html @@ -0,0 +1,469 @@ + + + + + + + Customer Checkout UI Redesign A/B Test Analysis + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Customer Checkout UI Redesign A/B Test Analysis

+ +
+
+

Can a checkout redesign boost conversion rates without lowering average order values? In this case study, we evaluate a 30-day e-commerce A/B test using a triad of statistical hypothesis tests - combining a Two-Sample Z-Test for Proportions, Shapiro-Wilk normality testing, and a Mann-Whitney U Test—to deliver a data-backed rollout recommendation.

+ +
+ +

Table of Contents

+ + + +
+ +

Project Overview

+


+

Context

+ +

An e-commerce retailer redesigned their customer checkout user interface (UI) in an effort to streamline the cart-to-purchase process and improve checkout conversion rates.

+ +

The campaign was run across a 30-day testing window where incoming site traffic was randomly split between two groups:

+
    +
  • The Control Group used the legacy UI to complete purchases
  • +
  • The Test Group used the new redesigned UI to complete purchases
  • +
+ +

The product and marketing teams logged daily performance metrics including number of items added to carts, number of completed purchases, and the total spent by customers on a day-to-day basis.

+ +

Objective: Determine if the redesigned UI significantly improves purchase conversion rates without negatively impacting Average Order Value (AOV).

+ +


+ +

Actions

+ +

To evaluate the performance of the UI redesign, as well as the impact on Average Order Value (AOV), we structured our analysis across three sequential hypothesis tests:

+ +
    +
  1. Primary Metric Evaluation (Conversion Rate): +
      +
    • Objective: Determine whether the new checkout UI drives a statistically significant lift in cart-to-purchase conversion rates.
    • +
    • Method: Run a Two-Sample Z-Test for Proportions with statsmodels.
    • +
    +
  2. +
  3. Normality Diagnostic (Daily Average Order Value): +
      +
    • Objective: Evaluate whether daily Average Order Value (spent/purchased) meets the parametric assumption of a normal distribution.
    • +
    • Method: Run a Shapiro-Wilk Test on daily AOV for both Control and Test groups using scipy.stats.
    • +
    +
  4. +
  5. Secondary Metric Evaluation (AOV Impact) +
      +
    • Objective: Assess whether customer spending per completed order changed significantly between UI variants.
    • +
    • Method: Select the final two-sample test based on the Shapiro-Wilk diagnostic. If the data is normally distributed, run the Welch’s t-Test. If the AOV data is non-parametric, run the Mann-Whitney U Test with. Use scipy.stats.
    • +
    +
  6. +
+ +


+ +

Results & Discussion

+ +

Our 30-day A/B experiment confirmed that the redesigned checkout UI significantly improves purchase completion without harming average order value.

+ +
    +
  • Conversion Rate Lift: Cart-to-purchase conversion increased from 40.21% (Control) to 59.13% (Test) with a +47.03% relative improvement. Z-Test of Proportions confirmed this difference is statistically significant (p-value [0.0000] < significance level [0.05])
  • +
  • Average Order Value (AOV): AOV saw a nominal increase of +$0.51 ($102.14 vs. $102.65), but non-parametric testing confirmed this difference is not statistically significant (p-value [0.2717] > significance level [0.05]).
  • +
+ +

Business Recommendation: Proceed with the complete rollout the redesigned UI to all customers. With the split-testing routing infrastructure already in place, the engineering effort to fully deploy the UI is minimal and carries negligible risk. Continue with further metric testing as detailed in the Discussion section of this report.

+ +
+ +

Data Overview & Preparation

+


+We’ll start by importing our two groups of data, the control group who used the legacy UI and the test group who used the new UI for purchasing items in their carts. This data contains sales metrics on each unique day during the 30 day trial in August.

+ +
import pandas as pd
+
+control = pd.read_csv('control_group.csv', sep = ';')
+test = pd.read_csv('test_group.csv', sep = ';')
+
+control = control.dropna()
+test = test.dropna()
+
+ +

We have the following columns of interest in both datasets:

+
    +
  • Date
  • +
  • Spend [USD]
  • +
  • # of Add to Cart
  • +
  • # of Purchase
  • +
+ +

Before applying statistical analyses, let’s simply aggregate the raw data to determine the conversion rates of items being put into carts to being purchased.

+ +
ctrl_conversion_rate = round(control['# of Purchase'].sum() / control['# of Add to Cart'].sum() * 100, 3)
+test_conversion_rate = round(test['# of Purchase'].sum() / test['# of Add to Cart'].sum() * 100, 3)
+
+relative_lift = (test_conversion_rate - ctrl_conversion_rate)/ctrl_conversion_rate * 100
+
+print(f'Control Group Conversion Rate = {ctrl_conversion_rate}%')
+print(f'Test Group Conversion Rate = {test_conversion_rate}%')
+print(f'Relative Lift = {relative_lift:.2f}%')
+
+>> Control Group Conversion Rate = 40.215%
+>> Test Group Conversion Rate = 59.128%
+>> Relative Lift = 47.03%
+
+ +

At first glance, basic conversion rate data aggregation suggests the newly deployed UI improved checkout performance, driving a +47.03% relative lift in conversion rate (rising from 40.22% in the Control group to 59.13% in the Test group).

+ +

Looking at the average order value (AOV) data, we seemed to only have a $0.51 increase in revenue, which so far suggests that the new UI did not negatively impact sales.

+ +
ctrl_AOV = round(control['Spend [USD]'].sum()/control['# of Purchase'].sum(), 2)
+test_AOV = round(test['Spend [USD]'].sum()/test['# of Purchase'].sum(), 2)
+
+print(f'Control AOV = ${ctrl_AOV}')
+print(f'Test AOV = ${test_AOV}')
+
+>> Control AOV = $4.41
+>> Test AOV = $4.92
+
+ +

However, raw descriptive statistics alone cannot determine whether these gains are statistically meaningful or the result of random sampling noise. To establish whether the redesigned UI genuinely drives conversion rate improvements, we evaluate these proportions using a Two-Sample Z-Test for Proportions.

+ +
+ +

Applying Z-Test for Proportions

+ +


+ +

State Hypotheses & Significance Level For Test

+ +

To kick off our Z-Test, we’ll need to define our Null Hypothesis, our Alternate Hypothesis, and our Significance Level. For our significance level, we’ll be using the commonly used value of 0.05 (or 5%), which will be carried through for all subsequent tests.

+ +
    +
  • Null Hypothesis: There is no significant relationship between the checkout UI version and the sales conversion rate. They are independent.
  • +
  • Alternate Hypothesis: There is a relationship between the checkout UI version and the sales conversion rate. They are not independent.
  • +
  • Significance Level: 0.05
  • +
+ +


+ +

Calculating the P-Value

+ +

We want to look at if the new UI led to a significant increase in conversion rate, from items added to the cart to the purchase step.

+ +
* If p-value >= 0.05: Fail to reject the null hypothesis. UI version and conversion rate are statistically independent
+* If p-value < 0.05: Reject the null hypothesis in favor of the alternate
+
+ +

To do this, we will statsmodels.stats.proportion library was used to import the proportion_ztest algorithm, to run our Z-Test. The results of this test will provide a p-value to be compared against our significance level.

+ +

Inputs of the proportions_ztest:
+–count represents the amount of successes for each dataset: it will be defined as the number of total purchases from each dataset.
+–nobs (ie. Number of Observations) will be the number of items added to carts.
+–alternative, looking if the % of signups is significantly higher, or larger

+ +
import numpy as np
+from statsmodels.stats.proportion import proportions_ztest
+
+purchases = [test["# of Purchase"].sum(), control["# of Purchase"].sum()]
+carts = [test["# of Add to Cart"].sum(), control["# of Add to Cart"].sum()]
+
+z_stat, p_val = proportions_ztest(
+    count=purchases, nobs=carts, alternative="larger"
+)
+
+print(f"Z-statistic: {z_stat:.4f}")
+print(f"p-value:     {p_val:.4f}")
+
+>> Z-statistic: 47.1959
+>> p-value:     0.0000
+
+ +

Our calculated p-value of 0.0000 is less than our set significance level of 0.05, which provides evidence to reject the null hypothesis in favor of the alternate. The 18.92% jump in cart-to-purchase conversion rate is statistically significant and virtually impossible to have happened by random chance!

+ +
+ +

Applying Shapiro-Wilk to Assess for Data Normality

+ +


+ +

To assess the effect the UI version had on the Average Order Value (AOV), we first need to determine whether to run a parametric or a non-parametric test through another hypothesis test known as the Shapiro-Wilk test.

+ +
    +
  • Null Hypothesis: The daily AOV data in both groups is normally distributed
  • +
  • Alternate Hypothesis: The daily AOV data in both groups is not normally distributed
  • +
  • Significance Level: 0.05
  • +
+ +

Parametric tests such as the standard two-sample t-test rely on the assumption of data normality to calculate standard errors and p-values; violating this assumption risks inflating Type I error rates. Running the Shapiro-Wilk test allows us to verify the data structure before selecting a model.

+ +

Calculating the P-Value

+ +
* If p-value >= 0.05: The daily AOV in both groups is normally distributed. Run Welch's *t*-test to assess statistical significance
+* If p-value < 0.05: The daily AOV data is not normally distributed. Run the Mann Whitney U to assess statistical significance
+
+ +

We’ll acquire the Shapiro-Wilk p-value outputs with the scipy library stats module.

+ +
from scipy import stats
+
+# Calculate AOV
+control["AOV"] = control["Spend [USD]"] / control["# of Purchase"]
+test["AOV"] = test["Spend [USD]"] / test["# of Purchase"]
+
+# Run Shapiro-Wilk on AOV
+stat_ctrl, p_ctrl = stats.shapiro(control["AOV"])
+stat_test, p_test = stats.shapiro(test["AOV"])
+
+print(f"Control AOV - W Stat: {stat_ctrl:.4f}, p-value: {p_ctrl:.4f}")
+print(f"Test AOV - W Stat: {stat_test:.4f}, p-value: {p_test:.4f}")
+
+>> Control AOV - W Stat: 0.9132, p-value: 0.0206
+>> Test AOV - W Stat: 0.8966, p-value: 0.0069
+
+ +

The p-values returned from the Shapiro-Wilk test are less than the significance level of 0.05 for both groups, indicating that the AOV data is not normally distributed.

+ +

Consequently, we proceed with the non-parametric Mann-Whitney U test, which compares distribution ranks rather than sample means and requires no distributional assumptions.

+ +
+ +

Applying Mann-Whitney U Test

+ +

We’ve now determined to run the Mann-Whitney U Test to assess our AOV between the control and test data.

+ +
    +
  • Null Hypothesis: There is no statistical difference in the distribution of daily AOV between the Control and Test groups
  • +
  • Alternate Hypothesis: There is a statistically significant difference in the distribution of daily AOV between the Control and Test groups
  • +
  • Significance Level: 0.05
  • +
+ +

Calculating the P-Value

+ +
* If p-value >= 0.05: There is statistically no difference in daily AOV between groups. Fail to reject the null hypothesis. 
+* If p-value < 0.05: There is a statistically significant difference in daily AOV between groups. Reject the null hypothesis.
+
+ +

With scipy stats already imported, we can run the mannwhitneyu algorithm on our data, then compare the returned p-value to our initial significance level.

+ +

Inputs of mannwhitneyu:
+–control["AOV"]
+–test["AOV"]
+–alternative: two-sided since we are looking at the difference between the control and test AOV.

+ +
u_stat, u_pvalue = stats.mannwhitneyu(
+    test["AOV"], control["AOV"], alternative="two-sided"
+)
+
+print(f"U-statistic: {u_stat:.4f}")
+print(f"p-value: {u_pvalue:.4f}")
+
+>> U-statistic: 508.0000
+>> p-value: 0.2717
+
+ +

The returned p-value for the Mann-Whitney U test is greater than our significance level, thus, we fail to reject the null hypothesis.

+ +

Based on the 30-day trial, there is no statistically significant difference in AOV across the two groups.

+ +
+ +

Analyzing The Results

+ +

Through the Z-Test of Proportions, we calculated:

+ +
p-value [0.0000] < significance level [0.05]
+
+ +

Thus, we reject the null hypothesis in favor of the alternate - indicating that there is a true relationship between the new UI and the increase in conversion rate, and that the observed relative lift was not due to chance.

+ +


+ +

Through the Mann-Whitney U test used to assess the statistical impact the new UI had on AOV, we determined the following:

+ +
p-value [0.2717] > significance level [0.05]
+
+ +

Thus, we fail to reject the null hypothesis - indicating that the new UI led to no statistically significant impact on AOV.

+ +


+ +

Conclusion: We can statistically conclude that the new UI led to more items purchased while not negatively impacting AOV.

+ +
+ +

Discussion

+ +

Our 30-day A/B experiment confirms that the redesigned checkout UI delivered a statistically significant boost in conversion rate without degrading customer average order values.

+ +

Cart-to-purchase conversion increased from 40.21% (Control) to 59.13% (Test), representing a +47.03% relative lift in checkout efficiency. While raw AOV showed a slight increase of $0.51 cents per order, non-parametric testing confirmed this difference is not statistically significant with a p-value of 0.2717.

+ +

Business Impact: These statistical conclusions support the business decision to roll out the redesigned UI to all customers. With the split-testing routing infrastructure already in place, the engineering effort to fully deploy the UI is minimal and carries negligible risk.

+ +

Next Steps:

+
    +
  • Monitor Long-Term AOV Trends Post-Rollout: Higher cart conversion in theory lays the groundwork for revenue growth over time. We recommend tracking AOV and total revenue across a 60–90 day post-launch window to evaluate whether increased purchase frequency translates into higher revenue.
  • +
  • Analyze Behavioral Flow Features for Website-wide Applicability: Analyze the new UI features (such as simplified fields, button placements, color schemes, widgets, etc) to determine which design features drove the highest lift. Then, identify ways to apply these effective design choices to other areas of the website.
  • +
+ + + + +
+ +
+ + + + + + +
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/_site/2026/08/15/Causal-Impact-Analysis.html b/_site/2026/08/15/Causal-Impact-Analysis.html new file mode 100644 index 000000000..2a77b46e4 --- /dev/null +++ b/_site/2026/08/15/Causal-Impact-Analysis.html @@ -0,0 +1,575 @@ + + + + + + + Quantifying Sales Uplift With Causal Impact Analysis + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +

Quantifying Sales Uplift With Causal Impact Analysis

+ +
+
+

Our grocery retailing client is back with another request! Following our initial Chi-Square Test of Independence test evaluating membership sign-up rates, leadership now wants to understand how the “Delivery Club” campaign impacted overall customer spending. In this project, we apply Causal Impact Analysis to quantify the net incremental revenue generated by the campaign and isolate true sales uplift from organic baseline trends.

+ +
+ +

Table of Contents

+ + + +
+ +

Project Overview

+


+ +

Context

+ +

In late June, a grocery retailer promoted their new “Delivery Club” membership campaign. For a $100 annual fee, members receive unlimited free grocery deliveries for an entire year, starting July 1st. Having previously evaluated the impact of campaign mailers on signup rates using a Chi-Square Test of Independence, leadership now needs to quantify the financial impact of the program. Specifically, the client wants to determine whether customers who signed up for the Delivery Club increased their spending in the months following the launch—and by how much.

+ +

Our core business hypothesis is that waiving delivery fees removes purchasing friction, encouraging Delivery Club members to shop more frequently and spend more per order over time. To measure this, we leverage a counterfactual framework using non-member spending behavior. Customers who chose not to sign up should in theory continue their normal shopping habits after July 1st. By calculating average daily sales for this non-member control group, we can establish a reliable baseline prediction of what Delivery Club members would have spent if the program had never existed.

+ +

Ultimately, our primary objective is to evaluate average daily spend across both customer cohorts from July 1 through September 30. Comparing actual member transactions against this synthetic baseline allows us to differentiate true campaign uplift from organic market growth and quantify the net incremental revenue directly caused by the membership launch.

+ +


+ +

Actions

+ +
    +
  • Environment & Data Setup: Import required analytical libraries (causalimpact, pandas). Load raw transaction and campaign datasets.
  • +
  • Data Integration & Aggregation: Merge customer transactions with campaign metadata on customer_id and aggregate daily spend into a unified customer time-series DataFrame.
  • +
  • Format Model Matrix: Pivot data to generate mean daily spend by group (signup_flag), explicitly ordering the treatment group (Delivery Club members) into the first column and non-members into the second.
  • +
  • Define Evaluation Windows: Set explicit date boundaries for the pre-intervention baseline period (2020-04-01 to 2020-06-30) and post-intervention assessment window (2020-07-01 to 2020-09-30).
  • +
  • Model Execution: Run the CausalImpact algorithm to generate a counterfactual baseline and calculate 95% Bayesian confidence intervals.
  • +
  • Performance Evaluation: +
      +
    • Utilize ci.plot() to visually assess daily spending trajectory, cumulative pointwise causal effects, and spend lift over time.
    • +
    • Run ci.summary() to extract numerical estimates for average daily lift, percentage increase, and total incremental revenue generated.
    • +
    +
  • +
+ +


+ +

Results & Discussion

+ +

The Delivery Club campaign successfully generated a statistically significant increase in customer spending over the 92-day evaluation window, fully confirming our core business hypothesis.

+ +
    +
  • Relative & Absolute Lift: Campaign members spent an average of $171.33 per day, compared to an estimated counterfactual baseline of $121.42 per day. This represents an absolute gain of +$49.92 per customer per day, and +41.11% relative uplift in daily spend.
  • +
  • Top-Line Revenue Contribution: Across all active members over the post-launch window, the program drove $4,592.49 in net incremental revenue ($15,762.69 actual spend vs. $11,170.20 predicted baseline).
  • +
  • Statistical Certainty: The model confirms a p-value of p = 0.0 (100% posterior causal probability), confirming with high statistical confidence that the sales uplift was directly caused by the Delivery Club launch rather than random noise or natural circumstances. The 95% Credible Interval bounds the true relative lift between +34.33% and +48.10%.
  • +
+ +
+ +

Concept Overview

+ +

Causal Impact Analysis is a statistical method built by researchers at Google in 2014. It predicts what would have happened if a treatment event (known as an intervention) never took place and compares that prediction to what actually happened with the observed data.

+ +

alt text

+ +

In the example plot image above, the following key components are represented:

+ +
    +
  • Observed (Red Line): This curve represents the true recorded metric over time, both before and after the “Change Made” treatment event. This is the treatment group.
  • +
  • Change Made (Vertical Line at t = 10): Represents the exact timestamp at which the treatment event took place. This datapoint separates the pre-period and post-period.
  • +
  • Pre-period (t < 10): The period of time before the treatment event. This timeframe is used to train the model on the relationship between target and control variables.
  • +
  • Post-period (t > 10): The period of time after the treatment event where the causal effect is measured.
  • +
  • Counterfactual Estimate (Blue Line): This curve represents an estimated prediction of what would have happened if the treatment event never occurred. It is created by feeding the CausalImpact algorithm control metrics that are not affected by the event.
  • +
  • Causal Effect (Purple Vertical Arrow): The delta between the Observed and Counterfactual Estimate curves, representing the absolute lift.
  • +
+ +


+ +

Model Setup Requirements

+ +

To run the analysis via the Python causalimpact package, input data must be structured as follows:

+
    +
  • Time Index: A DataFrame indexed by datetime.
  • +
  • Target Variable (Column 1): The response metric being evaluated (example: daily spend for the treatment group).
  • +
  • Control Variables (Columns 2+): One or more time-series metrics unaffected by the intervention used by the model to reconstruct the counterfactual baseline.
  • +
+ +

Performance Note for Large Datasets (tfcausalimpact): For multi-year or high-frequency datasets, the standard Python package can run slowly. Installing tfcausalimpact optimizes execution by translating your DataFrame into TensorFlow Probability structures (tfp.sts) to drastically accelerate Bayesian computations and avoid deprecation warnings.

+ +


+ +

Why Use Causal Impact Analysis?

+ +

Causal Impact Analysis automatically accounts for trend shifts, seasonality (e.g., weekends vs. weekdays), and historical patterns using Bayesian structural time-series models. It also quantifies uncertainty by outputting probabilistic confidence intervals alongside point estimates to make risk and impact clearly measurable.

+ +
+ +

Data Overview & Preparation

+ +

First, import the required packages for data processing and causal impact analysis:

+ +
from causalimpact import CausalImpact
+import pandas as pd
+
+ +

Next, we’ll import and merge our data tables of interest:

+ +
    +
  • +

    The transactions table contains individual customer transactions, with fields such as customer_id, transaction_date, transaction_id, num_items, and sales_cost

    +
  • +
  • +

    The campaign_data table contains data from the Delivery Club campaign, tracking which type of mailer each customer received (mailer_type) and whether they signed up or not (signup_flag).

    +
  • +
+ +
# Import data tables
+transactions = pd.read_excel('data/grocery_database.xlsx', sheet_name = 'transactions')
+campaign_data = pd.read_excel('data/grocery_database.xlsx', sheet_name = 'campaign_data')
+
+ +

Because the transactions table tracks data from April through September, daily sales serves as the appropriate time-series metric. We group the data by customer_id and transaction_date to aggregate individual daily spending into a new DataFrame: customer_daily_sales. Retaining customer_id at this stage allows us to successfully merge the aggregated sales numbers with our campaign_data table.

+ +
# Aggregate sales cost per customer per day
+customer_daily_sales = transactions.groupby(['customer_id', 'transaction_date'])['sales_cost'].sum().reset_index()
+
+# Merge data tables on customer_id
+customer_daily_sales = pd.merge(customer_daily_sales, campaign_data, how = 'inner', on = 'customer_id')
+
+ +


+Below is a 5-row sample of the imported customer_daily_sales DataFrame: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
customer_idtransaction_datesales_costcampaign_namecampaign_datemailer_typesignup_flag
42020-04-01 00:00:00193.96delivery_club2020-07-01 00:00:00Mailer11
52020-04-01 00:00:00755.77delivery_club2020-07-01 00:00:00Mailer21
222020-04-01 00:00:0021.83delivery_club2020-07-01 00:00:00Control0
372020-04-01 00:00:0053.72delivery_club2020-07-01 00:00:00Mailer10
422020-04-01 00:00:00319.36delivery_club2020-07-01 00:00:00Mailer11
+ +

To prepare the dataset for the CausalImpact algorithm, the customer_daily_sales DataFrame must meet the following requirements:

+
    +
  1. Datetime Index: The rows must be uniquely indexed by a continuous datetime sequence.
  2. +
  3. Column Ordering: The first column must represent the Target Variable (treatment group metric), followed by one or more Control Columns (unaffected baseline series).
  4. +
+ +

To achieve this, we can first pivot customer_daily_sales into a new DataFrame to aggregate average daily spend across both customer groups: members who signed up for the Delivery Club (signup_flag = 1, treatment) and members who did not (signup_flag = 0, control).

+ +
causal_impact_df = customer_daily_sales.pivot_table(index = 'transaction_date',
+                                                    columns = 'signup_flag',
+                                                    values = 'sales_cost',
+                                                    aggfunc = 'mean').round(2)
+
+# Define frequency for our DateTimeIndex ("D" = daily) 
+causal_impact_df.index.freq = "D"
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
transaction_date01
2020-04-0174.46194.49
2020-04-0275.56185.16
2020-04-0374.39118.12
2020-04-0463.00198.53
2020-04-0572.44145.46
+ +

Now that causal_impact_df is indexed by unique transaction_date, the two columns must be rearranged so that the impacted treatment group data occupies the first column position.

+ +
# For causal impact we need the impacted group in the first column (see required columns)
+causal_impact_df = causal_impact_df[[1,0]]
+
+# Rename columns for clarity
+causal_impact_df.columns = ["member", "non_member"]
+
+ +

Now, the causal_impact_df input data is correctly formatted and ready for model fitting:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
transaction_datemembernon_member
2020-04-01194.4974.46
2020-04-02185.1675.56
2020-04-03118.1274.39
2020-04-04198.5363.00
2020-04-05145.4672.44
+ +
+ +

Applying Causal Impact Analysis

+ +

Before running the CausalImpact algorithm, the pre_period and post_period must be defined in addition to the causal_impact_df input.

+ +

The pre_period represents the baseline timeframe prior to the Grocery Club campaign being launched. The post_period spans the timeframe immediately following the campaign launch.

+ +

Note on dataset: Although the campaign memberships last for a year, the available transactions data ends on 2020-09-30, defining the limit of the post_period evaluation window.

+ +
# Time period before the Delivery Club campaign
+pre_period = ["2020-04-01","2020-06-30"]
+# Evaluation window after campaign launch
+post_period = ["2020-07-01","2020-09-30"]
+
+# Fit the Causal Impact model
+ci = CausalImpact(causal_impact_df, pre_period, post_period)
+
+ +

Running this code fits the CausalImpact model and stores the statistical output inside the ci object. To evaluate the campaign’s performance, the following core methods will be used: .plot() to visualize the counterfactual trajectory and .summary() to quantify the absolute lift.

+ +
+ +

Analyzing The Results

+ +

Executing ci.plot() generates a three-panel visualization. Across all three subplots, the vertical dashed black line denotes the intervention date (2020-07-01), separating the model’s training window (pre-period) from the assessment window (post-period).

+ +
ci.plot()
+
+ +

alt text

+ +
    +
  1. Original vs. Counterfactual (First subplot): +
      +
    • Observed Data ('y', Solid Black Line): Shows the true recorded average daily sales for customers who joined the Delivery Club.
    • +
    • Counterfactual Prediction ('Predicted', Blue Dashed Line): Represents the estimated average daily spend if those customers had never joined the campaign.
    • +
    • Confidence Interval (Shaded Purple Region): Illustrates the 95% posterior probability interval around the prediction, which is the CausalImpact default.
    • +
    • Takeaway: Post-intervention, observed daily spend ('y') consistently trends above the baseline prediction, indicating a clear positive lift from the membership launch. Because the actual post-period spend consistently breaches the upper boundary of the purple band, we can conclude that the observed uplift in sales is statistically significant and extremely unlikely to be due to random noise.
    • +
    +
  2. +
  3. Point Effects (Second subplot): +
      +
    • Displays the daily delta between actual observations and the counterfactual baseline.
    • +
    • In the pre-period, point effects hover around zero as expected, verifying strong baseline alignment.
    • +
    • In the post-period, daily point effects jump into positive value territory, demonstrating that daily sales consistently surpassed expected baselines.
    • +
    +
  4. +
  5. Cumulative Effect (Third subplot): +
      +
    • Aggregates daily pointwise uplift over time into a running cumulative sum.
    • +
    • The steady upward slope throughout July, August, and September confirms that the campaign generated compounding incremental revenue, leading to several thousand dollars of total lift by the end of the evaluation window.
    • +
    +
  6. +
+ +

From a first look at the ci.plot, it seems as though customers who signed up for the campaign ended up spending more daily indicating a successful campaign!

+ +

We can further support our statistical prediction by running the ci.summary() functionality; this will help quantify the exact magnitude and statistical significance of the plotted lift:

+ +
# Extract the summary statistics & report
+print(ci.summary())
+
+>> Posterior Inference {Causal Impact}
+                          Average            Cumulative
+Actual                    171.33             15762.69
+Prediction (s.d.)         121.42 (4.26)      11170.2 (392.29)
+95% CI                    [112.93, 129.65]   [10389.73, 11927.46]
+
+Absolute effect (s.d.)    49.92 (4.26)       4592.49 (392.29)
+95% CI                    [41.69, 58.4]      [3835.23, 5372.96]
+
+Relative effect (s.d.)    41.11% (3.51%)     41.11% (3.51%)
+95% CI                    [34.33%, 48.1%]    [34.33%, 48.1%]
+
+Posterior tail-area probability p: 0.0
+Posterior prob. of a causal effect: 100.0%
+
+For more details run the command: print(impact.summary('report'))
+
+ +

This output summary table breaks down the evaluation window into two key dimensions: Average (daily per-customer metrics) and Cumulative (total aggregate metrics over the post-period)

+ +

Average Daily Impact:

+
    +
  • Actual vs. Predicted: Delivery Club members spent an average of $171.33 per day during the post-period, compared to a predicted counterfactual baseline of $121.42.
  • +
  • Absolute Effect: The campaign drove an incremental daily lift of $49.92 per member ($171.33 - $121.42).
  • +
  • Relative Effect: This daily lift represents a 41.11% proportional increase in average daily spending over baseline expectations.
  • +
+ +

Cumulative Total Impact:

+
    +
  • Across the entire 92-day post-period evaluation window, total actual spend for all campaign members reached $15,762.69, compared to the expected counterfactual total of $11,170.20.
  • +
  • This yields a total net incremental revenue generated by the campaign of $4,592.49.
  • +
+ +

Statistical Significance & Uncertainty:

+
    +
  • Confidence Intervals: The model establishes a 95% Credible Interval bounding the true relative lift between +34.33% and +48.10%. Reporting these bounds gives stakeholders a clear safety margin, confirming that even under the most conservative scenario, the campaign delivered at least a 34% sales boost.
  • +
  • P-Value & Causal Certainty: The model reports a p-value of p = 0.0 (100% causal probability). This confirms with extreme statistical certainty that the revenue jump was directly caused by the Delivery Club launch, rather than random noise or normal shopping fluctuations.
  • +
+ +
+ +

Discussion

+ +

Business Impact & Strategic Implications

+ +

The true value of Causal Impact Analysis lies in isolating real incremental gains from baseline sales trends. Standard transactional reporting would credit the entire $15,762.69 post-period spend to the Delivery Club. However, by constructing a synthetic counterfactual ($11,170.20), we isolated the true top-line contribution of the initiative to $4,592.49 in net incremental revenue.

+ +

Waiving delivery fees effectively removed purchasing friction for active members, driving an absolute gain of +$49.92 per customer per day through higher transaction frequencies and larger order values across the 92-day evaluation window.

+ +

From a financial planning perspective, this $4,592.49 net revenue lift serves as the exact top-line figure leadership needs for ROI modeling. By comparing this revenue gain directly against campaign execution costs—such as promotional mailer printing, delivery fee subsidies, and operational logistics—the business can measure exact campaign profitability and determine long-term program sustainability.

+ +

Next Steps

+ +
    +
  • Financial Planning & ROI Modeling: This $4,592.49 net revenue lift provides leadership with the precise top-line baseline required to evaluate overall campaign ROI against marketing costs, delivery fees, and operational expenses.
  • +
  • Customer Lifetime Value (LTV) Tracking: Extend the post-intervention evaluation window as additional transaction history becomes available to assess retention rates and determine whether daily spending lift persists throughout the full membership year.
  • +
  • Custom Plot Formatting: Enhance default ci.plot() outputs using custom matplotlib.pyplot styling to generate stakeholder-ready visualizations with clear color palettes, axes labels, and explicit legend labels.
  • +
+ + + + +
+ +
+ + + + + + +
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/_site/404.html b/_site/404.html new file mode 100644 index 000000000..c7a4254cf --- /dev/null +++ b/_site/404.html @@ -0,0 +1,112 @@ + + + + + + + Page not found + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+

Page not found

+
+
+
+
+
+

404

+

The requested page could not be found.

+

Back to the home

+
+
+
+
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/_site/README.md b/_site/README.md new file mode 100644 index 000000000..b882b22e4 --- /dev/null +++ b/_site/README.md @@ -0,0 +1 @@ +# my page! diff --git a/_site/README_2_run_local_instance.txt b/_site/README_2_run_local_instance.txt new file mode 100644 index 000000000..afe7aa0a4 --- /dev/null +++ b/_site/README_2_run_local_instance.txt @@ -0,0 +1,8 @@ +To run these git-pages locally, use the following commands from the terminal in the directory of the Gemfile + +Note: There is a dependency on Ruby for this to work + +>> bundle install +>> bundle exec jekyll serve --force_polling + +Open web browser to http://127.0.0.1:4000/ \ No newline at end of file diff --git a/_site/assets/css/main.css b/_site/assets/css/main.css new file mode 100644 index 000000000..78cf6e2dd --- /dev/null +++ b/_site/assets/css/main.css @@ -0,0 +1 @@ +@font-face{font-family:'Futura Md BT';src:url("../fonts/futura medium bt.tff") format("truetype");font-weight:normal;font-style:normal}/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}body,h1,h2,h3,h4,h5,h6,p,blockquote,pre,dl,dd,ol,ul,fieldset,legend,figure,hr{margin:0;padding:0}li>ul,li>ol{margin-bottom:0}table{border-collapse:collapse;border-spacing:0}h1,h2,h3,h4,h5,h6,ul,ol,dl,blockquote,p,address,hr,table,fieldset,figure,pre{margin-bottom:15px}ul,ol,dd{margin-left:15px}div.highlighter-rouge,figure.highlight{position:relative;margin-bottom:1em;background:#263238;color:#eff;font-family:Monaco,Consolas,"Lucida Console",monospace;font-size:.75em;line-height:1.8;border-radius:4px}div.highlighter-rouge>pre,div.highlighter-rouge pre.highlight,figure.highlight>pre,figure.highlight pre.highlight{margin:0;padding:1em}.highlight table{margin-bottom:0;font-size:1em;border:0}.highlight table td{padding:0;width:calc(100% - 1em);border:0}.highlight table td.gutter,.highlight table td.rouge-gutter{padding-right:1em;width:1em;color:#b2ccd6;border-right:1px solid #b2ccd6;text-align:right}.highlight table td.code,.highlight table td.rouge-code{padding-left:1em}.highlight table pre{margin:0}.highlight pre{width:100%}.highlight .hll{background-color:#eff}.highlight .c{color:#b2ccd6}.highlight .err{color:#f07178}.highlight .k{color:#c792ea}.highlight .l{color:#f78c6c}.highlight .n{color:#eff}.highlight .o{color:#89ddff}.highlight .p{color:#eff}.highlight .cm{color:#b2ccd6}.highlight .cp{color:#b2ccd6}.highlight .c1{color:#b2ccd6}.highlight .cs{color:#b2ccd6}.highlight .gd{color:#f07178}.highlight .ge{font-style:italic}.highlight .gh{color:#eff;font-weight:bold}.highlight .gi{color:#c3e88d}.highlight .gp{color:#b2ccd6;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#89ddff;font-weight:bold}.highlight .kc{color:#c792ea}.highlight .kd{color:#c792ea}.highlight .kn{color:#89ddff}.highlight .kp{color:#c792ea}.highlight .kr{color:#c792ea}.highlight .kt{color:#ffcb6b}.highlight .ld{color:#c3e88d}.highlight .m{color:#f78c6c}.highlight .s{color:#c3e88d}.highlight .na{color:#82aaff}.highlight .nb{color:#eff}.highlight .nc{color:#ffcb6b}.highlight .no{color:#f07178}.highlight .nd{color:#89ddff}.highlight .ni{color:#eff}.highlight .ne{color:#f07178}.highlight .nf{color:#82aaff}.highlight .nl{color:#eff}.highlight .nn{color:#ffcb6b}.highlight .nx{color:#82aaff}.highlight .py{color:#eff}.highlight .nt{color:#89ddff}.highlight .nv{color:#f07178}.highlight .ow{color:#89ddff}.highlight .w{color:#eff}.highlight .mf{color:#f78c6c}.highlight .mh{color:#f78c6c}.highlight .mi{color:#f78c6c}.highlight .mo{color:#f78c6c}.highlight .sb{color:#c3e88d}.highlight .sc{color:#eff}.highlight .sd{color:#b2ccd6}.highlight .s2{color:#c3e88d}.highlight .se{color:#f78c6c}.highlight .sh{color:#c3e88d}.highlight .si{color:#f78c6c}.highlight .sx{color:#c3e88d}.highlight .sr{color:#c3e88d}.highlight .s1{color:#c3e88d}.highlight .ss{color:#c3e88d}.highlight .bp{color:#eff}.highlight .vc{color:#f07178}.highlight .vg{color:#f07178}.highlight .vi{color:#f07178}.highlight .il{color:#f78c6c}.gist th,.gist td{border-bottom:0}.container{max-width:1200px;padding-left:15px;padding-right:15px;margin:0 auto}.container-full{max-width:100%;padding-left:15px;padding-right:15px;margin:0 auto}.row{display:flex;flex-wrap:wrap;flex:0 1 auto;flex-direction:row;box-sizing:border-box;margin-left:-15px;margin-right:-15px}.col{padding-left:15px;padding-right:15px}[class^="col-"]{flex:auto}.col-0{width:0%}.col-1{width:8.3333333333%}.col-2{width:16.6666666667%}.col-3{width:25%}.col-4{width:33.3333333333%}.col-5{width:41.6666666667%}.col-6{width:50%}.col-7{width:58.3333333333%}.col-8{width:66.6666666667%}.col-9{width:75%}.col-10{width:83.3333333333%}.col-11{width:91.6666666667%}.col-12{width:100%}.push-0{margin-left:0%}.push-1{margin-left:8.3333333333%}.push-2{margin-left:16.6666666667%}.push-3{margin-left:25%}.push-4{margin-left:33.3333333333%}.push-5{margin-left:41.6666666667%}.push-6{margin-left:50%}.push-7{margin-left:58.3333333333%}.push-8{margin-left:66.6666666667%}.push-9{margin-left:75%}.push-10{margin-left:83.3333333333%}.push-11{margin-left:91.6666666667%}.push-12{margin-left:100%}.pull-0{margin-right:0%}.pull-1{margin-right:8.3333333333%}.pull-2{margin-right:16.6666666667%}.pull-3{margin-right:25%}.pull-4{margin-right:33.3333333333%}.pull-5{margin-right:41.6666666667%}.pull-6{margin-right:50%}.pull-7{margin-right:58.3333333333%}.pull-8{margin-right:66.6666666667%}.pull-9{margin-right:75%}.pull-10{margin-right:83.3333333333%}.pull-11{margin-right:91.6666666667%}.pull-12{margin-right:100%}@media (min-width: 768px){.col-t-0{width:0%}.col-t-1{width:8.3333333333%}.col-t-2{width:16.6666666667%}.col-t-3{width:25%}.col-t-4{width:33.3333333333%}.col-t-5{width:41.6666666667%}.col-t-6{width:50%}.col-t-7{width:58.3333333333%}.col-t-8{width:66.6666666667%}.col-t-9{width:75%}.col-t-10{width:83.3333333333%}.col-t-11{width:91.6666666667%}.col-t-12{width:100%}.push-t-0{margin-left:0%}.push-t-1{margin-left:8.3333333333%}.push-t-2{margin-left:16.6666666667%}.push-t-3{margin-left:25%}.push-t-4{margin-left:33.3333333333%}.push-t-5{margin-left:41.6666666667%}.push-t-6{margin-left:50%}.push-t-7{margin-left:58.3333333333%}.push-t-8{margin-left:66.6666666667%}.push-t-9{margin-left:75%}.push-t-10{margin-left:83.3333333333%}.push-t-11{margin-left:91.6666666667%}.push-t-12{margin-left:100%}.pull-t-0{margin-right:0%}.pull-t-1{margin-right:8.3333333333%}.pull-t-2{margin-right:16.6666666667%}.pull-t-3{margin-right:25%}.pull-t-4{margin-right:33.3333333333%}.pull-t-5{margin-right:41.6666666667%}.pull-t-6{margin-right:50%}.pull-t-7{margin-right:58.3333333333%}.pull-t-8{margin-right:66.6666666667%}.pull-t-9{margin-right:75%}.pull-t-10{margin-right:83.3333333333%}.pull-t-11{margin-right:91.6666666667%}.pull-t-12{margin-right:100%}}@media (min-width: 992px){.col-d-0{width:0%}.col-d-1{width:8.3333333333%}.col-d-2{width:16.6666666667%}.col-d-3{width:25%}.col-d-4{width:33.3333333333%}.col-d-5{width:41.6666666667%}.col-d-6{width:50%}.col-d-7{width:58.3333333333%}.col-d-8{width:66.6666666667%}.col-d-9{width:75%}.col-d-10{width:83.3333333333%}.col-d-11{width:91.6666666667%}.col-d-12{width:100%}.push-d-0{margin-left:0%}.push-d-1{margin-left:8.3333333333%}.push-d-2{margin-left:16.6666666667%}.push-d-3{margin-left:25%}.push-d-4{margin-left:33.3333333333%}.push-d-5{margin-left:41.6666666667%}.push-d-6{margin-left:50%}.push-d-7{margin-left:58.3333333333%}.push-d-8{margin-left:66.6666666667%}.push-d-9{margin-left:75%}.push-d-10{margin-left:83.3333333333%}.push-d-11{margin-left:91.6666666667%}.push-d-12{margin-left:100%}.pull-d-0{margin-right:0%}.pull-d-1{margin-right:8.3333333333%}.pull-d-2{margin-right:16.6666666667%}.pull-d-3{margin-right:25%}.pull-d-4{margin-right:33.3333333333%}.pull-d-5{margin-right:41.6666666667%}.pull-d-6{margin-right:50%}.pull-d-7{margin-right:58.3333333333%}.pull-d-8{margin-right:66.6666666667%}.pull-d-9{margin-right:75%}.pull-d-10{margin-right:83.3333333333%}.pull-d-11{margin-right:91.6666666667%}.pull-d-12{margin-right:100%}}@media (min-width: 768px){.col-t-0{width:0%}.col-t-1{width:8.3333333333%}.col-t-2{width:16.6666666667%}.col-t-3{width:25%}.col-t-4{width:33.3333333333%}.col-t-5{width:41.6666666667%}.col-t-6{width:50%}.col-t-7{width:58.3333333333%}.col-t-8{width:66.6666666667%}.col-t-9{width:75%}.col-t-10{width:83.3333333333%}.col-t-11{width:91.6666666667%}.col-t-12{width:100%}.push-t-0{margin-left:0%}.push-t-1{margin-left:8.3333333333%}.push-t-2{margin-left:16.6666666667%}.push-t-3{margin-left:25%}.push-t-4{margin-left:33.3333333333%}.push-t-5{margin-left:41.6666666667%}.push-t-6{margin-left:50%}.push-t-7{margin-left:58.3333333333%}.push-t-8{margin-left:66.6666666667%}.push-t-9{margin-left:75%}.push-t-10{margin-left:83.3333333333%}.push-t-11{margin-left:91.6666666667%}.push-t-12{margin-left:100%}.pull-t-0{margin-right:0%}.pull-t-1{margin-right:8.3333333333%}.pull-t-2{margin-right:16.6666666667%}.pull-t-3{margin-right:25%}.pull-t-4{margin-right:33.3333333333%}.pull-t-5{margin-right:41.6666666667%}.pull-t-6{margin-right:50%}.pull-t-7{margin-right:58.3333333333%}.pull-t-8{margin-right:66.6666666667%}.pull-t-9{margin-right:75%}.pull-t-10{margin-right:83.3333333333%}.pull-t-11{margin-right:91.6666666667%}.pull-t-12{margin-right:100%}}@media (min-width: 992px){.col-d-0{width:0%}.col-d-1{width:8.3333333333%}.col-d-2{width:16.6666666667%}.col-d-3{width:25%}.col-d-4{width:33.3333333333%}.col-d-5{width:41.6666666667%}.col-d-6{width:50%}.col-d-7{width:58.3333333333%}.col-d-8{width:66.6666666667%}.col-d-9{width:75%}.col-d-10{width:83.3333333333%}.col-d-11{width:91.6666666667%}.col-d-12{width:100%}.push-d-0{margin-left:0%}.push-d-1{margin-left:8.3333333333%}.push-d-2{margin-left:16.6666666667%}.push-d-3{margin-left:25%}.push-d-4{margin-left:33.3333333333%}.push-d-5{margin-left:41.6666666667%}.push-d-6{margin-left:50%}.push-d-7{margin-left:58.3333333333%}.push-d-8{margin-left:66.6666666667%}.push-d-9{margin-left:75%}.push-d-10{margin-left:83.3333333333%}.push-d-11{margin-left:91.6666666667%}.push-d-12{margin-left:100%}.pull-d-0{margin-right:0%}.pull-d-1{margin-right:8.3333333333%}.pull-d-2{margin-right:16.6666666667%}.pull-d-3{margin-right:25%}.pull-d-4{margin-right:33.3333333333%}.pull-d-5{margin-right:41.6666666667%}.pull-d-6{margin-right:50%}.pull-d-7{margin-right:58.3333333333%}.pull-d-8{margin-right:66.6666666667%}.pull-d-9{margin-right:75%}.pull-d-10{margin-right:83.3333333333%}.pull-d-11{margin-right:91.6666666667%}.pull-d-12{margin-right:100%}}@media (min-width: 768px){.col-t-0{width:0%}.col-t-1{width:8.3333333333%}.col-t-2{width:16.6666666667%}.col-t-3{width:25%}.col-t-4{width:33.3333333333%}.col-t-5{width:41.6666666667%}.col-t-6{width:50%}.col-t-7{width:58.3333333333%}.col-t-8{width:66.6666666667%}.col-t-9{width:75%}.col-t-10{width:83.3333333333%}.col-t-11{width:91.6666666667%}.col-t-12{width:100%}.push-t-0{margin-left:0%}.push-t-1{margin-left:8.3333333333%}.push-t-2{margin-left:16.6666666667%}.push-t-3{margin-left:25%}.push-t-4{margin-left:33.3333333333%}.push-t-5{margin-left:41.6666666667%}.push-t-6{margin-left:50%}.push-t-7{margin-left:58.3333333333%}.push-t-8{margin-left:66.6666666667%}.push-t-9{margin-left:75%}.push-t-10{margin-left:83.3333333333%}.push-t-11{margin-left:91.6666666667%}.push-t-12{margin-left:100%}.pull-t-0{margin-right:0%}.pull-t-1{margin-right:8.3333333333%}.pull-t-2{margin-right:16.6666666667%}.pull-t-3{margin-right:25%}.pull-t-4{margin-right:33.3333333333%}.pull-t-5{margin-right:41.6666666667%}.pull-t-6{margin-right:50%}.pull-t-7{margin-right:58.3333333333%}.pull-t-8{margin-right:66.6666666667%}.pull-t-9{margin-right:75%}.pull-t-10{margin-right:83.3333333333%}.pull-t-11{margin-right:91.6666666667%}.pull-t-12{margin-right:100%}}@media (min-width: 992px){.col-d-0{width:0%}.col-d-1{width:8.3333333333%}.col-d-2{width:16.6666666667%}.col-d-3{width:25%}.col-d-4{width:33.3333333333%}.col-d-5{width:41.6666666667%}.col-d-6{width:50%}.col-d-7{width:58.3333333333%}.col-d-8{width:66.6666666667%}.col-d-9{width:75%}.col-d-10{width:83.3333333333%}.col-d-11{width:91.6666666667%}.col-d-12{width:100%}.push-d-0{margin-left:0%}.push-d-1{margin-left:8.3333333333%}.push-d-2{margin-left:16.6666666667%}.push-d-3{margin-left:25%}.push-d-4{margin-left:33.3333333333%}.push-d-5{margin-left:41.6666666667%}.push-d-6{margin-left:50%}.push-d-7{margin-left:58.3333333333%}.push-d-8{margin-left:66.6666666667%}.push-d-9{margin-left:75%}.push-d-10{margin-left:83.3333333333%}.push-d-11{margin-left:91.6666666667%}.push-d-12{margin-left:100%}.pull-d-0{margin-right:0%}.pull-d-1{margin-right:8.3333333333%}.pull-d-2{margin-right:16.6666666667%}.pull-d-3{margin-right:25%}.pull-d-4{margin-right:33.3333333333%}.pull-d-5{margin-right:41.6666666667%}.pull-d-6{margin-right:50%}.pull-d-7{margin-right:58.3333333333%}.pull-d-8{margin-right:66.6666666667%}.pull-d-9{margin-right:75%}.pull-d-10{margin-right:83.3333333333%}.pull-d-11{margin-right:91.6666666667%}.pull-d-12{margin-right:100%}}@media (min-width: 992px){.d-hide{display:none !important}.d-show{display:block !important}}*,*::after,*::before{box-sizing:border-box}body{font-family:"Nunito",sans-serif;font-size:16px;line-height:28px;color:#f6f6f6;background:#2C2F36;overflow-x:hidden;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body input,body textarea{border:#3E4250 1px solid;outline:none}body input:focus:required:invalid,body textarea:focus:required:invalid{border-color:#e02f40}body input:required:valid,body textarea:required:valid{border-color:#34a74e}::placeholder{color:#666}*::selection{color:#fff;background-color:#1ABC9C}h1,h2,h3,h4,h5,h6{line-height:initial}h1{font-size:36px}h2{font-size:28px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}blockquote{padding-left:15px;border-left:3px solid #1ABC9C;font-style:normal}pre{overflow:auto;padding:15px;margin-bottom:0;font-size:14px;white-space:pre-wrap;word-wrap:break-word;word-break:break-all}img{max-width:100%;height:auto;vertical-align:middle}img+em{text-align:center;display:block;margin-top:10px;font-weight:normal;font-size:16px;color:#707890}a{text-decoration:none;color:#1ABC9C;transition:.35s}a:hover{color:#3ee4c4}hr{display:block;height:2px;padding:0;margin:30px 0;line-height:0;border:0;background-color:#3E4250}blockquote{padding:15px 15px 15px 30px;font-size:18px;font-style:normal;border-left:4px solid #1ABC9C}blockquote p{margin-bottom:0}pre{overflow:auto;padding:15px;margin-bottom:0;font-size:14px;white-space:pre-wrap;word-wrap:break-word;word-break:break-all}.table-container{max-width:100%;overflow-x:auto}table{font-size:12px;color:#101010;width:100%;border-width:1px;border-color:#707890;border-collapse:collapse}table th{padding:8px;font-size:16px;text-align:left;border:1px solid #707890;color:#fff;background-color:#1ABC9C}table tr{background-color:#c5f7ed;transition:all .3s ease}table tr:nth-child(even){background-color:#fff}table td{padding:8px;font-size:14px;border:1px solid #707890}table tr:hover{background-color:#fff}.button{display:inline-block;white-space:nowrap;vertical-align:middle;font:inherit;text-align:center;padding:8px 30px;border-radius:3px;cursor:pointer;transition:.35s}.button--primary{color:#fff;background-color:#1ABC9C}.button--primary:hover{background-color:#117964;color:#fff;transition:.35s}.button--big{display:block;width:100%}.pagination{margin:20px 0 40px}.pagination .pagination-list{display:flex;align-items:center;text-align:center}.pagination .pagination-list .older-posts,.pagination .pagination-list .previous-posts,.pagination .pagination-list .newer-posts{display:inline-block;flex-grow:1}.pagination .pagination-list .older-posts .older-link,.pagination .pagination-list .older-posts .previous-link,.pagination .pagination-list .older-posts .newer-link,.pagination .pagination-list .previous-posts .older-link,.pagination .pagination-list .previous-posts .previous-link,.pagination .pagination-list .previous-posts .newer-link,.pagination .pagination-list .newer-posts .older-link,.pagination .pagination-list .newer-posts .previous-link,.pagination .pagination-list .newer-posts .newer-link{display:flex;justify-content:center;align-items:center;padding:20px 35px;font-size:14px;line-height:18px;font-weight:bold;color:#f6f6f6;background:rgba(35,36,39,0.95);transition:.35s}.pagination .pagination-list .older-posts .older-link:hover,.pagination .pagination-list .older-posts .previous-link:hover,.pagination .pagination-list .older-posts .newer-link:hover,.pagination .pagination-list .previous-posts .older-link:hover,.pagination .pagination-list .previous-posts .previous-link:hover,.pagination .pagination-list .previous-posts .newer-link:hover,.pagination .pagination-list .newer-posts .older-link:hover,.pagination .pagination-list .newer-posts .previous-link:hover,.pagination .pagination-list .newer-posts .newer-link:hover{background:rgba(28,29,31,0.95)}.pagination .pagination-list .older-link i{margin-left:10px}.pagination .pagination-list .previous-link i,.pagination .pagination-list .newer-link i{margin-right:10px}@media only screen and (min-width: 768px){.pagination{margin:20px 0}}.footer{padding:50px 0;margin-top:30px;background:#101010}.footer-top{display:flex;justify-content:space-between;align-items:center;padding-bottom:15px;margin-bottom:10px;border-bottom:1px solid #3E4250}.footer-top .logo-text{font-size:16px;font-weight:700;line-height:16px;text-transform:uppercase;letter-spacing:5px;color:#f6f6f6}.footer-top .logo-text:hover{color:#707890}.footer-top .top{display:flex;justify-content:center;align-items:center;width:30px;height:30px;border:1px solid #2C2F36;background-color:#2C2F36;color:#f6f6f6;cursor:pointer;transition:.35s;opacity:.5}.footer-top .top:hover{opacity:1}.footer-bottom{display:flex;align-items:center;flex-wrap:wrap}.footer-bottom .copyright{margin-right:60px}.footer-bottom .copyright p{margin-bottom:0;font-size:13px;color:#e7e7e7}.footer-bottom .copyright p a{color:#e7e7e7}.footer-bottom .copyright p a:hover{color:#1ABC9C}.footer-bottom .footer-social ul li{display:inline-block;margin-left:15px}.footer-bottom .footer-social ul li:first-child{margin-left:0}.footer-bottom .footer-social ul li a{font-size:13px;font-weight:bold;color:#e7e7e7;transition:.35s}.footer-bottom .footer-social ul li a:hover{color:#1ABC9C}.header{margin-bottom:50px;background:#101010;padding:40px 0 0}.header-box{display:flex;flex-direction:row;justify-content:space-between;align-items:center;color:#e7e7e7}.header-avatar{display:block;margin:auto;border-radius:100%;width:100px;overflow:hidden;margin-bottom:20px;border:4px solid #fff}@media only screen and (min-width: 768px){.header-avatar{width:200px;margin-bottom:60px}}.header-avatar:hover{-moz-transform:scale(1.05);-o-transform:scale(1.05);-ms-transform:scale(1.05);-webkit-transform:scale(1.05);transform:scale(1.05)}.header-title{font-family:"Futura Md BT",sans-serif;font-size:27px;font-weight:500;letter-spacing:0em;line-height:1;text-align:right}@media only screen and (min-width: 768px){.header-title{font-size:32px}}@media only screen and (min-width: 1220px){.header-title{font-size:40px}}.header-subtitle{font-family:"Futura Md BT",sans-serif;font-size:18px;font-weight:500;letter-spacing:0em;text-align:right;border-bottom:4px solid #e7e7e7;margin-bottom:20px}@media only screen and (min-width: 768px){.header-subtitle{font-size:32px}}@media only screen and (min-width: 1220px){.header-subtitle{font-size:40px}}.menu-links .list-reset{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;margin-top:60px}.menu-links .list-reset .item{list-style:none}.menu-links .list-reset .item .item-link{color:#e7e7e7;font-size:18px}.menu-links .list-reset .item .item-link:hover{color:#1ABC9C}.header-tagline{margin-top:10px;font-family:"Nunito",sans-serif;font-size:16px;font-weight:400;letter-spacing:0em;line-height:1.5}@media only screen and (min-width: 768px){.header-tagline{margin-top:15px;font-size:18px}}@media only screen and (min-width: 1220px){.header-tagline{font-size:18px}}.article-first{margin-bottom:30px;background:#101010}.article-first .article-image-first{position:relative;display:flex;align-items:center;min-height:450px;background-size:cover;background-position:center;background-repeat:no-repeat;background-color:#101010;box-shadow:0 9px 18px 0 rgba(0,0,0,0.25)}.article-first .article-image-first:before{content:"";display:table;padding-top:50%}.article-first .article-content-first{padding:20px;color:#f6f6f6}.article-first .article-content-first .article-tag .tag{font-size:12px}.article-first .article-content-first .article-date .date{font-size:12px;color:rgba(246,246,246,0.8)}.article-first .article-content-first .article-title{margin-bottom:20px;font-size:30px;line-height:30px;font-weight:normal}.article-first .article-content-first .article-title a{color:#f6f6f6;transition:.35s}.article-first .article-content-first .article-excerpt{max-width:450px;margin-bottom:30px;font-size:14px;line-height:23px}.article-first .article-content-first .button{font-size:14px;font-weight:700;border:1px solid #c9d3e7;color:#f6f6f6;transition:.35s}.article-first .article-content-first .button:hover{border:1px solid #1ABC9C;background:#1ABC9C;color:#fff}.article{display:flex;margin-bottom:25px}.article .article-box{padding-bottom:20px;border-bottom:2px solid #3E4250}.article .article-head{background:#101010}.article .article-image{position:relative;display:block;margin-bottom:20px;background-size:cover;background-position:center;background-repeat:no-repeat;background-color:#101010;box-shadow:0 9px 18px 0 rgba(0,0,0,0.25)}.article .article-image:before{content:"";display:table;padding-top:75%}.article .article-content .article-info{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;margin-bottom:5px}.article .article-content .article-info .article-date{font-size:12px;color:rgba(246,246,246,0.8)}.article .article-content .article-info .article-tag .tag{display:inline-block;padding:2px 5px;margin-right:5px;font-size:12px;line-height:18px;font-weight:bold;border:1px solid #1ABC9C;border-radius:3px;text-transform:capitalize;color:#1ABC9C;transition:.35s}.article .article-content .article-info .article-tag .tag:last-child{margin-right:0}.article .article-content .article-info .article-tag .tag:hover{background:#1ABC9C;color:#fff}.article .article-content .article-title{margin-bottom:5px}.article .article-content .article-title a{font-size:24px;color:#f6f6f6}.article .article-content .article-excerpt{margin-bottom:0;font-size:15px;line-height:25px;color:#e7e7e7}.article-box{position:relative;overflow:hidden}.article-first .article-image-first,.article-box .article-image{position:relative;opacity:0;transition:all 1s cubic-bezier(0.6, 0.3, 0, 1)}.article-first.reveal-in .article-image-first,.article-box.reveal-in .article-image{opacity:1}@media only screen and (min-width: 576px){.article-first .article-content-first .article-title{font-size:45px;line-height:45px}.article-first .article-content-first .article-excerpt{margin-bottom:50px}.menu-list .menu-link{font-size:50px;line-height:80px}.menu-list .menu-link:before{height:8px}}@media only screen and (min-width: 768px){.article-first .article-content-first{padding:60px 40px 0}}@media only screen and (max-width: 480px){.header{padding:20px 0 0;margin-bottom:30px}.header-box{flex-flow:wrap}.header-box hr{width:100%;background-color:transparent;margin:0}.wd-mob{width:100%}.header-avatar{margin:auto;margin-bottom:25px}.header-title{font-size:20px;text-align:center}.header-subtitle{font-size:16px;text-align:center;border-bottom:2px solid #e7e7e7}.menu-links .list-reset{margin-top:20px}.menu-links .list-reset .item{list-style:none;width:calc(100% / 2)}}.post{margin-bottom:20px}.post-image-box{background:#101010}.post-image-box.reveal-in .post-image{opacity:1}.post-image{min-height:430px;margin-bottom:30px;background-size:cover;background-position:center;background-repeat:no-repeat;background-color:#101010;box-shadow:0 9px 18px 0 rgba(0,0,0,0.25);position:relative;opacity:0;transition:all 1s cubic-bezier(0.6, 0.3, 0, 1)}.post-image:after{content:"";display:table;width:100%;padding-top:50%}.post-head,.post-content{background:rgba(35,36,39,0.95)}.post-content{padding:40px 15px;box-shadow:0 9px 18px 0 rgba(0,0,0,0.25)}.post-head,.post-body{margin-bottom:20px;border-bottom:1px solid #3E4250}.post-head{padding-bottom:30px}.post-head .post-tag{margin-bottom:20px;text-align:center}.post-head .post-tag .tag{display:inline-block;padding:5px 15px;margin-right:5px;font-size:12px;line-height:18px;font-weight:bold;border:1px solid #1ABC9C;text-transform:capitalize;color:#1ABC9C;transition:.35s}.post-head .post-tag .tag:last-child{margin-right:0}.post-head .post-tag .tag:hover{background:#1ABC9C;color:#fff}.post-head .post-title{font-size:28px;text-align:center;font-weight:normal;line-height:28px;margin-bottom:5px}.post-head .post-time{text-align:center}.post-head .post-time span{font-size:12px}.post-body{padding-bottom:20px}.post-share{padding:10px 0}.post-share .share-item{display:inline-block;padding-right:15px}.post-share .share-item a{color:#1ABC9C}.post-share .share-item a:hover{color:#3ee4c4}.post-share .share-item a i.ion{font-size:20px}.post-navigation{display:flex;flex-wrap:wrap;margin-top:30px}.prev,.next{flex-basis:100%;padding:15px;background-color:#1ABC9C;color:#f6f6f6}.prev:hover,.next:hover{background:#3ee4c4;color:#fff}.prev:hover .post-nav-title,.next:hover .post-nav-title{color:#1ABC9C;transition:.35s}.prev .post-nav-arrow,.next .post-nav-arrow{font-size:14px}.prev .post-nav-title,.next .post-nav-title{font-size:18px;color:#f6f6f6;transition:.35s}.prev{margin-bottom:10px}.prev,.next{text-align:center}.disabled{color:rgba(255,249,252,0.5);pointer-events:none;cursor:not-allowed}.comments{padding:0 15px;background:#232427}@media only screen and (min-width: 576px){.post-content{padding:40px}.post-head .post-title{font-size:43px;line-height:43px;margin-bottom:10px}.post-navigation{display:flex;justify-content:space-between;flex-wrap:nowrap}.prev,.next{flex-basis:48%;padding:20px}.prev .post-nav-title,.next .post-nav-title{font-size:21px}.prev{text-align:left;margin-bottom:0}.next{text-align:right}}.page{margin-bottom:20px}.page-image-box{background:#101010}.page-image-box.reveal-in .page-image{opacity:1}.page-image{min-height:230px;margin-bottom:30px;background-size:cover;background-position:center;background-repeat:no-repeat;background-color:#101010;box-shadow:0 9px 18px 0 rgba(0,0,0,0.25);opacity:0;transition:all 1s cubic-bezier(0.6, 0.3, 0, 1)}.page-image:after{content:"";display:table;width:100%;padding-top:50%}.page-content{padding:40px 15px;box-shadow:0 9px 18px 0 rgba(0,0,0,0.25);background:rgba(35,36,39,0.95)}.page-head{padding-bottom:30px}.page-head .page-title{font-size:28px;text-align:center;font-weight:normal;line-height:28px;margin-bottom:0}.page-head,.page-body{margin-bottom:20px;border-bottom:1px solid #3E4250}.page-body{padding-bottom:20px}@media only screen and (min-width: 576px){.page-content{padding:40px}.page-head .page-title{font-size:43px;line-height:43px;margin-bottom:10px}}.tags-list{display:flex;flex-wrap:wrap;margin:40px 0;padding:0;list-style:none}.tags-list .tags-item{margin:5px}.tags-list .tags-link{display:inline-block;padding:7px 12px;font-size:14px;border-radius:4px;color:#f6f6f6;font-weight:700;background:#2C2F36}.tags-list .tags-link:hover{color:#fff;background:#1ABC9C}.tags-title{margin-bottom:5px}.tags-group{margin-bottom:30px}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.vertical-center{display:flex;align-items:center;justify-content:center}.show{display:block !important}.hide{display:none !important}.invisible{visibility:hidden}.float-left{float:left}.float-right{float:right}.no-padding{padding:0}.no-margin{margin:0}.clearfix::after,.clearfix ::before{content:"";display:table;clear:both}.list-reset{list-style-type:none;margin:0;padding:0} diff --git a/_site/assets/fonts/Futura Bold Italic font.ttf b/_site/assets/fonts/Futura Bold Italic font.ttf new file mode 100644 index 000000000..095bbdff6 Binary files /dev/null and b/_site/assets/fonts/Futura Bold Italic font.ttf differ diff --git a/_site/assets/fonts/Futura Bold font.ttf b/_site/assets/fonts/Futura Bold font.ttf new file mode 100644 index 000000000..0725025e5 Binary files /dev/null and b/_site/assets/fonts/Futura Bold font.ttf differ diff --git a/_site/assets/fonts/Futura Book Italic font.ttf b/_site/assets/fonts/Futura Book Italic font.ttf new file mode 100644 index 000000000..99cd8a6ad Binary files /dev/null and b/_site/assets/fonts/Futura Book Italic font.ttf differ diff --git a/_site/assets/fonts/Futura Book font.ttf b/_site/assets/fonts/Futura Book font.ttf new file mode 100644 index 000000000..8abcf1591 Binary files /dev/null and b/_site/assets/fonts/Futura Book font.ttf differ diff --git a/_site/assets/fonts/Futura Extra Black font.ttf b/_site/assets/fonts/Futura Extra Black font.ttf new file mode 100644 index 000000000..f6f1b3946 Binary files /dev/null and b/_site/assets/fonts/Futura Extra Black font.ttf differ diff --git a/_site/assets/fonts/Futura Heavy Italic font.ttf b/_site/assets/fonts/Futura Heavy Italic font.ttf new file mode 100644 index 000000000..8a49a3695 Binary files /dev/null and b/_site/assets/fonts/Futura Heavy Italic font.ttf differ diff --git a/_site/assets/fonts/Futura Heavy font.ttf b/_site/assets/fonts/Futura Heavy font.ttf new file mode 100644 index 000000000..97eded13a Binary files /dev/null and b/_site/assets/fonts/Futura Heavy font.ttf differ diff --git a/_site/assets/fonts/Futura Light Italic font.ttf b/_site/assets/fonts/Futura Light Italic font.ttf new file mode 100644 index 000000000..cfb5ce413 Binary files /dev/null and b/_site/assets/fonts/Futura Light Italic font.ttf differ diff --git a/_site/assets/fonts/Futura Light font.ttf b/_site/assets/fonts/Futura Light font.ttf new file mode 100644 index 000000000..250162f49 Binary files /dev/null and b/_site/assets/fonts/Futura Light font.ttf differ diff --git a/_site/assets/fonts/Futura Medium Italic font.ttf b/_site/assets/fonts/Futura Medium Italic font.ttf new file mode 100644 index 000000000..81878d5f4 Binary files /dev/null and b/_site/assets/fonts/Futura Medium Italic font.ttf differ diff --git a/_site/assets/fonts/Futura XBlk BT.ttf b/_site/assets/fonts/Futura XBlk BT.ttf new file mode 100644 index 000000000..06a53a8e1 Binary files /dev/null and b/_site/assets/fonts/Futura XBlk BT.ttf differ diff --git a/_site/assets/fonts/Futura-CondensedLight.otf b/_site/assets/fonts/Futura-CondensedLight.otf new file mode 100644 index 000000000..aebc4d947 Binary files /dev/null and b/_site/assets/fonts/Futura-CondensedLight.otf differ diff --git a/_site/assets/fonts/futur.ttf b/_site/assets/fonts/futur.ttf new file mode 100644 index 000000000..cde2c37ad Binary files /dev/null and b/_site/assets/fonts/futur.ttf differ diff --git a/_site/assets/fonts/futura light bt.ttf b/_site/assets/fonts/futura light bt.ttf new file mode 100644 index 000000000..3fb654f15 Binary files /dev/null and b/_site/assets/fonts/futura light bt.ttf differ diff --git a/_site/assets/fonts/futura medium bt.ttf b/_site/assets/fonts/futura medium bt.ttf new file mode 100644 index 000000000..1ef199e91 Binary files /dev/null and b/_site/assets/fonts/futura medium bt.ttf differ diff --git a/_site/assets/fonts/futura medium condensed bt.ttf b/_site/assets/fonts/futura medium condensed bt.ttf new file mode 100644 index 000000000..7c3fe9e37 Binary files /dev/null and b/_site/assets/fonts/futura medium condensed bt.ttf differ diff --git a/_site/assets/fonts/sharefonts.net.txt b/_site/assets/fonts/sharefonts.net.txt new file mode 100644 index 000000000..17a37c1c4 --- /dev/null +++ b/_site/assets/fonts/sharefonts.net.txt @@ -0,0 +1 @@ +Free download fonts at http://sharefonts.net \ No newline at end of file diff --git a/_site/assets/fonts/tt0205m_.ttf b/_site/assets/fonts/tt0205m_.ttf new file mode 100644 index 000000000..3dc05d949 Binary files /dev/null and b/_site/assets/fonts/tt0205m_.ttf differ diff --git a/_site/assets/fonts/unicode.futurab.ttf b/_site/assets/fonts/unicode.futurab.ttf new file mode 100644 index 000000000..0c18339e1 Binary files /dev/null and b/_site/assets/fonts/unicode.futurab.ttf differ diff --git a/_site/assets/fonts/unicode.futurabb.ttf b/_site/assets/fonts/unicode.futurabb.ttf new file mode 100644 index 000000000..68422b109 Binary files /dev/null and b/_site/assets/fonts/unicode.futurabb.ttf differ diff --git a/_site/assets/js/common.js b/_site/assets/js/common.js new file mode 100644 index 000000000..8951a22bd --- /dev/null +++ b/_site/assets/js/common.js @@ -0,0 +1,61 @@ +$(function() { + 'use strict'; + + /* ======================= + // Reveal Image + ======================= */ + var wh = window.innerHeight; + + $(window).ready(function () { + $('body').waitForImages({ + finished: function () { + setTimeout(function () { + $('.preloader').addClass('hide'); + + setTimeout(function () { + reveals(); + }, 100); + }, 500); + }, + waitForAll: true + }); + }); + + function reveals() { + $(window).on('scroll', function () { + $(".article-box, .article-first, .post-image-box, .page-image-box, .post-body img, .page-body img, .recent-header").each( + function(i) { + var el_top = $(this).offset().top, + win_bottom = wh + $(window).scrollTop(); + + if (el_top < win_bottom) { + $(this) + .delay(i * 100) + .queue(function() { + $(this).addClass("reveal-in"); + }); + } + } + ); + }).scroll(); + } + + + /* ======================= + // Responsive Videos + ======================= */ + $(".post-content, .page-content").fitVids({ + customSelector: ['iframe[src*="ted.com"]'] + }); + + + /* ======================= + // Scroll Top Button + ======================= */ + $(".top").click(function () { + $("html, body") + .stop() + .animate({ scrollTop: 0 }, "slow", "swing"); + }); + +}); diff --git a/_site/assets/js/jquery-3.3.1.min.js b/_site/assets/js/jquery-3.3.1.min.js new file mode 100644 index 000000000..4d9b3a258 --- /dev/null +++ b/_site/assets/js/jquery-3.3.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.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||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(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&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(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&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!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=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.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=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.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=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.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=w.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=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.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)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.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=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.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=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(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(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.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=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.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)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(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):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.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}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.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||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.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 zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===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]}function Ut(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];o=c.shift();while(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}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,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":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)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 i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!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];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in 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(Vt[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()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.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 w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(" + + + + + + \ No newline at end of file diff --git a/_site/robots.txt b/_site/robots.txt new file mode 100644 index 000000000..d29706404 --- /dev/null +++ b/_site/robots.txt @@ -0,0 +1 @@ +Sitemap: http://localhost:4000/sitemap.xml diff --git a/_site/sitemap.xml b/_site/sitemap.xml new file mode 100644 index 000000000..626b1c7e4 --- /dev/null +++ b/_site/sitemap.xml @@ -0,0 +1,29 @@ + + + +http://localhost:4000/2026/05/15/Prime-Numbers-Finder.html +2026-05-15T00:00:00-04:00 + + +http://localhost:4000/2026/06/12/Earthquake-Tracker-Tableau.html +2026-06-12T00:00:00-04:00 + + +http://localhost:4000/2026/06/30/Chi-Square-Test.html +2026-06-30T00:00:00-04:00 + + +http://localhost:4000/2026/08/01/E-Commerce-AB-Test.html +2026-08-01T00:00:00-04:00 + + +http://localhost:4000/2026/08/15/Causal-Impact-Analysis.html +2026-08-15T00:00:00-04:00 + + +http://localhost:4000/ + + +http://localhost:4000/tags/ + + diff --git a/_site/tags/index.html b/_site/tags/index.html new file mode 100644 index 000000000..a6f634f30 --- /dev/null +++ b/_site/tags/index.html @@ -0,0 +1,406 @@ + + + + + + + Tags in Projects + + + + + + + + + + + +
+
+
+
+
+
+ + + Antonia Iaquinta + + +
+
+
+ +

Antonia Iaquinta

+ + +

Data Science & Analytics Portfolio

+ + +

Hi I'm Tonii, an engineer by trade, looking to pivot my career into data science and analytics! My portfolio contains interesting projects I've recently undertaken, with a strong emphasis on business impact. Please visit my Github & LinkedIn pages (or download my Resume) by using the links below!

+ + +
+
+
+
+
+
+ +
+
+
+
+
+

Tags in Projects

+
+
+ + + +
+ + +
+ + + +

AB Testing

+
+ +
+
+ Customer Checkout UI Redesign A/B Test Analysis | + + + + + 9 minute read + +
+
+ + + +
+ + +

Causal Impact Analysis

+
+ +
+
+ Quantifying Sales Uplift With Causal Impact Analysis | + + + + + 12 minute read + +
+
+ +
+ + +

Chi-Square

+
+ + + +
+ + +

Data Viz

+
+ +
+
+ Earthquake Tracker - Tableau Dashboard | + + + + + 1 minute read + +
+
+ +
+ + +

Hypothesis Testing

+
+ +
+
+ Customer Checkout UI Redesign A/B Test Analysis | + + + + + 9 minute read + +
+
+ + + +
+ + +

Mann-Whitney U

+
+ +
+
+ Customer Checkout UI Redesign A/B Test Analysis | + + + + + 9 minute read + +
+
+ +
+ + +

Primes

+
+ +
+
+ Prime Numbers Finder with Python | + + + + + 6 minute read + +
+
+ +
+ + +

Python

+
+ +
+
+ Quantifying Sales Uplift With Causal Impact Analysis | + + + + + 12 minute read + +
+
+ +
+
+ Customer Checkout UI Redesign A/B Test Analysis | + + + + + 9 minute read + +
+
+ + + +
+
+ Prime Numbers Finder with Python | + + + + + 6 minute read + +
+
+ +
+ + +

Shapiro-Wilk

+
+ +
+
+ Customer Checkout UI Redesign A/B Test Analysis | + + + + + 9 minute read + +
+
+ +
+ + +

Tableau

+
+ +
+
+ Earthquake Tracker - Tableau Dashboard | + + + + + 1 minute read + +
+
+ +
+ + +

Z-Test

+
+ +
+
+ Customer Checkout UI Redesign A/B Test Analysis | + + + + + 9 minute read + +
+
+ +
+ +
+
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/antonia_iaquinta_resume.docx b/docs/antonia_iaquinta_resume.docx new file mode 100644 index 000000000..f1bb33448 Binary files /dev/null and b/docs/antonia_iaquinta_resume.docx differ diff --git a/img/my_headshot.jpg b/img/my_headshot.jpg new file mode 100644 index 000000000..b94d7a45e Binary files /dev/null and b/img/my_headshot.jpg differ diff --git a/img/posts/AB_testing.jpg b/img/posts/AB_testing.jpg new file mode 100644 index 000000000..02ae94368 Binary files /dev/null and b/img/posts/AB_testing.jpg differ diff --git a/img/posts/Causal_Impact_Example.jpg b/img/posts/Causal_Impact_Example.jpg new file mode 100644 index 000000000..59cce40e3 Binary files /dev/null and b/img/posts/Causal_Impact_Example.jpg differ diff --git a/img/posts/checkout_UI.jpg b/img/posts/checkout_UI.jpg new file mode 100644 index 000000000..90840edb7 Binary files /dev/null and b/img/posts/checkout_UI.jpg differ diff --git a/img/posts/ci_plot.jpg b/img/posts/ci_plot.jpg new file mode 100644 index 000000000..333bcecce Binary files /dev/null and b/img/posts/ci_plot.jpg differ diff --git a/img/posts/coffee_python.jpg b/img/posts/coffee_python.jpg deleted file mode 100644 index 02d7aff37..000000000 Binary files a/img/posts/coffee_python.jpg and /dev/null differ diff --git a/img/posts/connect4_console.jpg b/img/posts/connect4_console.jpg new file mode 100644 index 000000000..68fc93fd6 Binary files /dev/null and b/img/posts/connect4_console.jpg differ diff --git a/img/posts/prime_nums.jpg b/img/posts/prime_nums.jpg new file mode 100644 index 000000000..9e7019783 Binary files /dev/null and b/img/posts/prime_nums.jpg differ diff --git a/img/posts/primes_image.jpeg b/img/posts/primes_image.jpeg deleted file mode 100644 index a4ea7f558..000000000 Binary files a/img/posts/primes_image.jpeg and /dev/null differ diff --git a/img/posts/quake_dash.jpg b/img/posts/quake_dash.jpg new file mode 100644 index 000000000..4ce7b44f1 Binary files /dev/null and b/img/posts/quake_dash.jpg differ diff --git a/img/profile_picture.JPG b/img/profile_picture.JPG deleted file mode 100644 index 156acc7a7..000000000 Binary files a/img/profile_picture.JPG and /dev/null differ