Ultra-targeted advertising: You win.

For years I have scoffed at browser extensions like AdBlock, recognizing the high costs of scaling websites that millions of us get access to at no cost. Facebook, for example, has hundreds of employees, over twenty thousand servers to maintain and 500,000,000 users. I've been using Facebook since 2004 and I've never given them as much as a dime.

An attempt to repay the thousands of free sites and apps I've used over the years, I decided I'd try to be a decent internet citizen.

But now I'm passed that stage of my life.

Ultra-targeted advertising seems to be a fairly new feature of Google's ad platform. You've most likely seen it before: you browse a few camera lenses at B&H Photo's website and leave. The next day you visit Wired.com, or anywhere that serves Google ad content, and you are greeted with a fully customized advertisement with images of those same few lenses you browsed the day before. It's no coincidence.

I find myself immaturely clicking the over-zealous advertisements with no intention of purchasing from B&H, knowing good-well that somebody is paying for that click.

I even vow to myself never to purchase from a site that takes part in these ultra-targeted advertisements (all-the-while crossing my fingers that I don't catch an essential site, like Amazon, joining in on the unpleasantness).

But now I'm passed that stage of my life too.

Hello AdBlock.

Don't Hate The Player: Hacking a TV Gameshow

(download)

I serendipitously started my car 38 minutes into a 60 minute radio segment of This American Life, just as Act Four began.

Shawn Allee tells a story of the oldest kind of million dollar idea, the scam. Or was it an honest venture? Back in the 1980s Michael Larson made the most money ever on the game show Press Your Luck. And it was no accident. Larson had a plan to get rich that surprised everyone.

For brevity's sake, I snipped out the first three acts, which leaves us with a ~20 minute piece about a damn fine hack.

As the old adage goes: Even a broken clock is right twice a day.

The audio is:

© 1995-2010 WBEZ Alliance, Inc. & Ira Glass. This American Life is produced by Chicago Public Radio and distributed by Public Radio International.

A/B Testing beyond registration

To follow up on a previous blog post, Declaring an A/B test goal after N visits or logins., we decided to make it easier to delay the reporting of a goal when A/B testing.

A goal is the event you are trying to increase in volume, for instance a user registration.

Use case [code at the end]:

Your site requires a user to register and login. You decide to A/B test some copy, perhaps you change the tone of the copy or you mention some different features that might appeal to a visitor more.

The user registers and you declare a goal. At the end of the test, you see which version performed better and you make the winning copy permanent. After you take into consideration the number of users that login once and never come back, did your previous test actually matter?

Sure, you signed up 8% more visitors, but if the number of hit-it-and-quit-it users increases as well, you might not see any increase in user engagement.

One possible solution: do NOT report the A/B test goal immediately after registration. Instead, delay the reporting of the goal until the user has used a certain feature, or has logged in N number of times.

press9 makes this simple. By just adding a delay parameter to the goal() function, we can set a number of times an action has to happen before you declare a goal.

By adding this snippet to the user home page( the page that your user might hit after logging in ), a goal won't be reported until this page has been hit N times, in this case 3.

Optionally, add this code to your page layout to A/B test User Experience(UX). For example, you might be A/B testing the navigation controls for your site. To test whether or not a visitor is more engaged, you can set the delay parameter to the minimum desired page view count per visitor.

press9 is making A/B testing tools for developers and designers. Bend, twist, hack - our tools should help you A/B test, not hinder.

Check us out

A/B Testing with WordPress

Press9wordpress_crop
More and more companies are using WordPress as a portal to their companies, whether it landing pages or employee blogs.

We've put together a fancy WordPress plugin which allows users to run A/B tests within WordPress in no time at all, with no additional database tables or strain on your servers. No redirecting, no URL tagging and no subdomaining.

The Plugin URI: http://info.press9formoreoptions.com/wordpress

After installing the plugin, log into press9 and click 'Create A Test'.

Enter the URL of the page(or pages) you wish to test into our visual Test Designer.

Create a variation of the page by editing HTML or CSS (or skip the editor and use custom javascript), and copy the generated code into the press9 A/B Testing WordPress plugin administrator console.

That's all there is to it.

You can create an account with press9 here.

press9 is building A/B testing tools for developers and designers. We have plugins for WordPress and Ruby on Rails, as well as Python, Ruby and PHP modules.

Our API offers the ability to track custom metrics in real time, and an awesome visual Test Designer.

A/B Testing with Ruby on Rails, ABingo and Press 9

It is now possible to do server-side A/B testing in the Rails framework with press 9.

ABingo is a great Rails plug-in to run server-side A/B tests and press 9 has adapted it to report data back to your press 9 account, still in real-time.

Here is a demo Ruby on Rails application running our version of ABingo.

The demo includes a live A/B test and installation instructions.

 

Press 9 is currently in a private beta. We're building A/B testing tools for developers and designers. Come check us out.

 

Declaring an A/B test goal after N visits or logins.

In a recent blog post, Eric Ries, an A/B testing advocate, warns of the futilty of declaring an A/B test goal too early (before you get a visitor to try your product):

"So even if you want to test a new button color, don’t measure the click-through rate on that button! Instead, ask yourself: “why do we care that customers click that button?” If it’s a “Register Now” button, it’s because we want customers to sign up and try the product. So let’s measure the percentage of customers who try the product. If the button color change doesn’t have an impact there – it’s too small, and should be reverted. Over time, this discipline helps us ignore the minor stuff and focus our energies on learning what will make a significant impact

You should try to chose a goal beyond the initial registration, visit or button click. The number of users that will hit-it-and-quit-it, or use the product once and never come back, is often the staggering majority of users for web applications. Therefor if our goal is just a signup, the A/B test results and conversion rate can be tainted by these users (unless your hit-it-and-quit-it numbers are low).

If you have a subscription-based site that requires user registration:

As an example, let's say we are split testing some marketing copy on a landing page for a subscription-based web application. Instead of declaring a goal after registration, let's declare a goal after the user has been to their home page 3 times.

Using press 9, a script like this on the home page would work just fine:

We're simply setting a cookie with a string on their first visit and increasing the length of that string until we get to the 3rd visit. You can change "visit_goal" to however many times you want a user to hit a certain page.

You could also include this script in the layout and declare a goal after a user has been to 20, or 30 pages.

If you have a blog or content-based site and you want to A/B test reader loyalty:

Our example goal is having a visitor visit 5 of your blog posts and only declaring a goal after that. Visitors who only see 3 or 4 blog posts will not count towards your conversion rate for this test(you can tweak the numbers any way you'd like). 

You'll embed the previous script on a in a layout file or blog container - it's up to you where it goes. Change the "visitor_goal" variable to 5.

Conclusion

Pick smart goals before you start an A/B test.

With press 9's A/B testing tools, it's easy for developers and designers to chose how the data for an A/B test is collected.

press 9 is currently in private beta. If you're interesting in A/B testing, come check us out at http://press9formoreoptions.com/

We offer a developer API, real time data and an interactive test designer.

Deploying CouchDB: Maintaining access to Futon

Difficulty: 4/10 (Moderately Easy)
Uses: CouchDB, NginX, Linux

Futon
One of the luxuries of developing an application with CouchDB as a datastore is Futon.

Futon is a web administrator interface to CouchDB. I've yet to write a view that didn't start off as a Temporary View in Futon.

Developing the application locally, with the default CouchDB settings, anybody can access Futon on a running instance of CouchDB by hitting: http://localhost:5984/_utils in your browser.

Adding an admin user to the CouchDB internal user database gets your a password prompt inside of Futon, but I'm still not comfortable with having port 5984(or whichever port you run CouchDB on) being open to the public.

We use NginX as a front end for our entire deployment. It's great, fast, and most importantly - stable.

Here are the steps for maintaing access to Futon on a deployment server via NginX.

Make sure CouchDB is bound to 127.0.0.1, not your public IP.(verify in /etc/couchdb/local.ini and /etc/couchdb/default.ini)

First - block off all ports you don't want to be public facing. This includes 5984 to the public. We'll do this through with IPTables on a Linux Linode server.

This article will give you an awesome step-by-step to do block non-essential ports: https://help.ubuntu.com/community/IptablesHowTo

We'll need to make one change to our IPTables: before we drop all(it's okay if you've already run "iptables -A INPUT -j DROP", just repeat the instructions in the next step for "--dport 8000"), we'll open up port 8000.

iptables -A INPUT -p tcp --dport 8000 -j ACCEPT

Next we'll create a User-Pass combo for our HTTP-Auth-Basic. Check to see if you have 'htpasswd' installed by entering 'htpasswd' on the command line. If you don't, 'sudo apt-get install apache2' will install 'htpasswd'.

In a non-public directory( I choose /opt ), we'll create a single user for HTTP-Auth-Basic:

htpasswd -c /opt/access_list mark

You'll be prompted to enter and then reenter a password.

Now we'll create a server entry in NginX, in the configuration file which is usually /etc/nginx/nginx.conf Add this block within the html {} block.

Replace 'yourdomain.com' with your domain or IP, and /opt/access_list with whatever you entered after "-c" in the previous htpasswd step.

Make sure the syntax is correct before restarting:

/etc/init.d/nginx configtest

If it passes:

/etc/init.d/nginx restart

Now go to http://yourdomain.com:8000 in a browser.

You should be prompted for the username and password you entered in the htpasswd step by NginX, not CouchDB.

Afterwards, you should see:

{"couchdb":"Welcome","version":"0.10.0"}

Now go to http://yourdomain.com:8000/_utils and viola, Futon is accessable on a deployed server while keeping your database safeguarded from the public.

Part 2: Visualizing log files with MongoDB, MapReduce, Ruby & Google Charts(& Flot)

Check out our latest A/B Testing application

In Part 1 of this series we set up our environment with MongoDB, MapReduce, Ruby and Sinatra  to extract some data out of our application log files and display them graphically on a web server. In Part 2 we'll be generating an activity chart for requests per day(or week, hour), like this:

Media_httpwwwmarkdima_dbvos

The big change will be in the mapping function. With MapReduce, the Map function runs first and prepares the data for the Reduce function.
Earlier our mapping function looked like:


Another look at a line from our data set reveals that "time" is stored as in integer:

Changing "this.uploadType" to "this.time" in our mapping function won't bode well for us since we'd have a different data point for every unique second.

Doing a little bit of arithmetic on this time value during the mapping stage of MapReduce will allow us to group many requests values under 1 chronilogical key. This will give us usable and chartable data.

Our new map function:

This map function says to map requests by the day, or 86,400 seconds. We could use 604,800 seconds for a week, or 3,600 seconds to group requests by the hour. By rounding each timestamp down to the day, we can see how many requests took place during those 86,400 seconds.


Our Chart class now looks like:

Testing it out:

To chart these results I found Google Charts to be rather lacking, so I'll be using Flot, a jquery-based javascript graph program. Flot produces gorgeous graphs and has a very rich feature set.

To install flot on a Sinatra web server, create a directory called public inside of stats/. "cd" to the new directory, and run "wget http://flot.googlecode.com/files/flot-0.6.tar.gz". Then extract that with "tar xvzf flot-0.6.tar.gz".

Now we need to link the javascript files into our webpage. We do this by creating another .haml file named layout.haml, which is a reserved name. Every haml template will attempt to render layout.haml first, unless specified with ":layout => false".

Here is stats/views/layout.haml:

And here is our updated stats/views/index.haml:

As you can tell the template now contains the necessary javascript to render a flot time chart. It is even possible to select certain areas of the chart to zoom in for a more detailed look at the data.

The data from the latest MapReduce needs to be converted into an string so our template can render it properly. The data format that flot is expecting looks like:

Our new stats/server.rb file is set up like so:

We multiply the time by 1000 for UTC time formatting. (I actually don't know why this is necessary, but the flot docs say to do it).
Flot also handles spacing out time, and converting time as an integer to a readable date.

Now we restart the server and check out our new stats page.

Media_httpwwwmarkdima_dbvos

For more information:
http://apirate.posterous.com/visualizing-log-files-with-mongodb-mapreduce (part 1 of this series)
http://code.google.com/p/flot/