Mandala

Procreate has a new(ish) feature that lets you draw in symmetrical patterns. I love it. Here’s one fun mandala I did.

Project FART: How Ads Work

My study about Fake Ad Results from Twitter (project FART) continues. Data from Twitter’s reports is unreliable, and data from their API fails occasionally as well. But here’s a little background:

The way I have my ads set up is that my “objective” is to get followers, and so that’s all I pay for. So, Twitter shows my ad, but I don’t get charged for it just to show up. I don’t get charged if someone likes or retweets it – I only have to pay if someone follows me. How much does it cost per follow? That’s a very sticky subject given the questionable integrity of Twitter’s numbers, but it varies, and over time it’s averaged around $.82 per follower. It depends on the time of day, who else is buying ads, etc.

Anyway: when you set up an ad with Twitter, you put in a daily budget. My daily budget for this ad is $10, and so that means Twitter shows my ads until people click and follow me enough that I hit that $10 cap – and then Twitter stops showing the ads. So, theoretically, if I make an ad and nobody follows from it, it would get shown endlessly and I would not be charged. But something tells me they wouldn’t let that happen.

This is a screenshot of the only info Twitter provides about how many people have seen your ads and what you’re being charged for. (Not very detailed, if you ask me.) “Impressions” means the number of times the ad was shown, follows, likes, etc. are all pretty self explanatory. “Spend” is the amount I was charged in a given hour, and “cost per follow” is the average cost for each follower gained during that hour.

Today’s Discrepancy

These are some of the notifications I got earlier today about my ad tweet. I made sure to include the time stamp on this image – if you look, you’ll see that all the alerts about those ads occurred within the last hour, so they should show up in Twitter’s data for the 5:00 PM hour. Buuuuut…

From 5-6PM ET, according to notifications on Twitter’s web interface, there were at least 3 interactions with my ad, but according to their ad results, they did not show my ad once. The ad was also apparently dead during the two hours before and after 5 PM.

What does this tell us? Nothing we don’t already know. Twitter’s numbers are unreliable. But this is the first time they’ve screwed up in a way that benefits me, so I’m not complaining!

Want to help with expenses? Check out: paypal.me/skweeds

Stormy Daniels

I feel like I should use the word photoshop because that’s the word people understand, but now I’m not actually using photoshop anymore, so I guess we’ll just say this is an image manipulation I made.

I can do this kind of stuff, if you need it.

Twitter advertising “results”

So, one of the things that has made my twitter advertising quest so absolutely maddening is the lack of data they provide about what they’re charging me for.

They’ve got a bunch of vague analytics on one page, but there’s a disclaimer at the bottom:

The data reported on this page is an estimate, and should not be considered official for billing purposes.

Great, so that gets them out of the responsibility of keeping up to date stats. Let’s check out the bills, that must be where the real data jackpot is.

Nope, nothing. So, I taught myself how to program with Twitter’s API and so over the last few weeks I’ve been able to set up a system that automatically gets lists of who follows me and when, which was super fun.

Below you can see a side-by-side comparison of the real results next to Twitter’s reported (bullshit) numbers.

The actual data is based on the only data I’ve been able to get. Here are the details from my 8/26 logs.

The experiment continues. I think I’ve got my app set up to run without issues while I’ll be out of the country, but I’d be lying if I said I thought I could really stay away from this stuff on vacation.

I plan to continue this project indefinitely. I’m using my own money to pay for this, and I don’t make any money off the ads I show*. If you’d like to help with my experiment, please donate below:

https://paypal.me/skweeds

*For the record, I also don’t make any money off of this or any other website I’ve created. I’ve been doing websites since Angelfire in the late 90’s, and to get free hosting you always had to compromise by showing someone else’s ads… well, after I turned 15, I decided I’d be a big girl and pay for my own hosting so I’d never have to show outside ads in my place. If you are paying for web hosting and your website is showing ads, you’re being ripped off.

Script: randomize 100

So! This is a script I first tried to write in 2010 but gave up because I had no idea how. Today, I remembered that it was something I’d wanted to do, and gave it another go… and voilà!

There was a software several years back called EyeQ, which was supposed to help your reading speed and focus.
One of the exercises was that every day, the software would generate a board with the numbers 1 – 100, and you had to find them all in order, and the amount of time that took was somehow reflective of your cognitive abilities.

Sadly, EyeQ is no more, but if you want to do the find-the-numbers bit, here’s a script you can run to make a randomized chart in any given Google spreadsheet. This is a super rough draft, but it does what I want it to, and I’m putting it out there in case anyone might have any use for it. Have fun!

function scatter100(){
var ss = SpreadsheetApp.getActive();
var sheetName = '100Rando';
if(ss.getSheetByName(sheetName)) { var sheet = ss.getSheetByName(sheetName); }
else { var sheet = ss.insertSheet(sheetName); }
ss.setActiveSheet(sheet);
var valsArray1d = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.']
var numVals = valsArray1d.length;
var workRange = sheet.getRange(1, 25, numVals, 1);
var vertOutArray = [];
for (var a = 0; a < numVals; a++){
var thisItem = valsArray1d[a];
var newArray = [thisItem];
vertOutArray.push(newArray)
}
workRange.setValues(vertOutArray);
workRange.randomize();
var mixedArray = workRange.getValues().toString().split(',');
for(var b = 0; b < 20; b++){ var multiplier = b*20;
var startVal = multiplier + 0; var endVal = 20 + multiplier;
var thisRow = mixedArray.slice(startVal, endVal);
var outRange = sheet.getRange(1 + b, 1, 1, 20);
var rangedArray = [thisRow];
outRange.setValues(rangedArray);
}
workRange.clear()
var rowsToDel = sheet.getMaxRows() - 20;
var colsToDel = sheet.getMaxColumns() - 20;
sheet.deleteColumns(21, colsToDel);
sheet.deleteRows(21, rowsToDel);
sheet.setColumnWidths(1, 20, 50);
sheet.setRowHeights(1, 20, 45);
var wholeSheet = sheet.getRange(1, 1, 20, 20);
wholeSheet.setFontSize(18);
wholeSheet.setFontWeight('bold')
wholeSheet.setVerticalAlignment('middle')
wholeSheet.setHorizontalAlignment('center')
wholeSheet.setBackground('#333');
wholeSheet.setFontColor('#cFF')
}

Twitter ad data: bad.

Twitter’s reported data vs. actual

Discrepancies exist between actual activity and activity in analytics repots. No getting around it.

I’d guess that major twitter advertisers see discrepancies of 2-5%. so knowing that I’d have significantly less activities, I expected discrepancies of maybe 10-20%.

I was WAY off. Here’s the email* I sent to Twitter explaining the situation.

Hi – there is nothing in your help files that matches my issue.
The data Twitter is giving me on followers is wrong
It’s not off, it’s straight up WRONG. 

As of 4:00 PM ET, Twitter analytics says I have gotten 60 follows and 42 unfollows for 8/6/18.

This is not true. 
In the last day, I have gotten 22 new followers, with 2 unfollows. The data is provided below.

Count/Follow time/Username
1.  8/6/18 15:04 @breysstory_
2.  8/6/18 14:50 @Didgeridood
3.  8/6/18 14:48 @AngelR95
4.  8/6/18 14:47 @gmiller1643
5.  8/6/18 14:28 @BeakersBro
6.  8/6/18 14:15 @farkas_jerry
7.  8/6/18 14:14 @pinkyapplepie
8.  8/6/18 14:03 @tacomike750*   unfollowed: 8/6/18 14:05
9.  8/6/18 13:52 @DanSumners1
10.  8/6/18 13:45 @xoxomeb
11.  8/6/18 13:45 @Flash94710833*   unfollowed: 8/6/18 13:45
12.  8/6/18 13:34 @BarbBondVO
13.  8/6/18 13:05 @Meghanb33
14.  8/6/18 12:56 @smittylou55
15.  8/6/18 12:33 @adaytoroberta17
16.  8/6/18 12:33 @ArtMcDonald11
17.  8/6/18 12:26 @reed180a
18.  8/6/18 12:19 @alexhibbert
19.  8/6/18 11:35 @leelarose100
20.  8/6/18 11:25 @Dani3lTravieso
21.  8/6/18 11:05 @SeymourZoe
22.  8/6/18 9:35 @raymond50128623

All of these followers were acquired on the calendar day 8/6/2018.
Even so, calendar day aside, there were no followers added yesterday after 3PM ET. 

SO: Accounting for any time changes, in the last 24 hours:
Twitter reports: 60 new followers.
In reality, there were: 22 new followers.

This is a discrepancy of more than 63%.

I’ve got my account set so that I am paying for followers, with my budget at $40 per day.
Analytics says that between my 2 campaigns, I am paying an average of $.67 per follow.
$.67 per follow isn’t bad. Mathematically, it’s right… if you’re using the incorrect follower count from Twitter.

Given the actual follower statistics (even including the unfollows), I am paying an average of $1.81 per follow.
So, per follower, the price I am being charged is 170% higher than the cost-per-follower fee shown to me by Twitter. 
That’s right – I’m being charged almost THREE TIMES what Twitter says I’m being charged.

Where are the missing followers?

Will Twitter change its data reporting to reflect reality?

Thanks.
Amanda Kruel

The actual follower data was pulled via Twitter’s API, which enabled me to collect data on all my followers once a minute.

Fun fact:
When you look for help on Twitter, you are asked to search their database.

If you search their database but don’t find what you’re looking for, you go to the reporting page and select that the issue is a data discrepancy and click to report it.

Instead of giving you any fields to fill and submit, they once again show you a list of articles about data discrepancies.

If you still can’t find what you’re looking for (it wasn’t there the first two times, so why would it be?), *then* they let you submit a report.

Fine. I submit one and instantly get a confirmation email with a case number back.

If you’re like me, you see the subject and think “Great, case number means it’s submitted, so they’ll email me back when they’ve got an answer.” And then you forget about it for a while, and then you notice it’s been several hours and you still don’t have an answer. So you open up that confirmation email, and … surprise!

Thank you for writing in. We see you have a question on Dashboards, Analytics & Ads Editor, under ‘I have another dashboard or analytics question.’ Your unique ticket number is: 90333778.

To save you time, many advertisers can get instant answers to questions on this topic through these articles in our Help Centre…

It’s not an email saying that my issue was submitted. It’s an email giving links to the same articles already linked to twice above. 🙄
But wait! All hope isn’t lost! Beneath that, it says:

If this has not answered your question, please reply to this case with more detail and we will try to provide more specific guidance. If you do not reply we will go ahead and close your case.

So, the “CASE #” in the email subject isn’t the number of an open case, it’s the number of a hypothetical case they’ll open only if I reply…

OK, so I just email back some details and then they’ll work on my case and get back to me, sounds good… Except the next line:

Please be aware that we cannot offer custom support to all queries.

🤦‍♀️
OK. Great. Guess I’ll wait and see if they care about the little guy.

I’m still collecting data, we’ll see how these discrepancies continue over time.
I plan to continue this experiment indefinitely. I’m using my own money to pay for this, and I don’t make any money off the ads I show. If you’d like to help with my experiment, please donate below:

https://paypal.me/skweeds

To contact me, email skweeds@gmail.com or visit my Twitter.

Twitter advertising

So, I’ve recently started advertising on twitter, not because I have a business to promote, just because I’m curious as to how it works.

It’s been a lot of fun, and it’s definitely interesting.
I’ve been looking at bits of data here and there, and I wasn’t really sure what to post, but I found some discrepancies that I believe merit pointing out.

What I’m paying for is followers. How it works:
Twitter shows my ad (which is a tweet I’ve written and chosen to “promote”).

My promoted tweet

Here is one of my ads.
When it shows up, there are several things that can happen.

  1. User ignores tweet, keeps scrolling.
  2. User views tweet or views my profile.
  3. User likes or retweets my tweet
  4. User follows me.Since the purpose of my campaign is to get followers, the only situation in which Twitter will charge me is number 4.

So, does it work? Let’s take a look. Here are some results.

Interesting. So, 241 follows, but 178 unfollows. That would be a net increase of 63 followers, right? Well, let’s take a look at my followers recently.

Now, wait a second! That shows only a net increase of 33 followers. What!?

Let’s take a look at the API data I’ve brought into my google spreadsheet.

You can’t see from that zoomed-out image, but there’s definitely a change in followers between the 4th and the 6th.

That data is really rough. I standardized it by calling once every 15 minutes, but if there really are all these apparent unfollows, I’m gonna have to pull data more often.
So, I have every-15-minutes data from 8/5/18 at 9 AM ET – 8/6/18 at 4:17 AM ET.

After that point, I have data for every minute (and it’s continuing to update).

Not much to show or tell right now, but I haven’t gotten a new follower since around 3:00 PM ET 8/5/18 (yesterday, ~18 hours ago).

I’ve emailed Twitter’s tech support twice about some of these discrepancies, but I have yet to hear back.

I’m going to update more in the future as I obtain more data. Hope you find this as interesting as I do!

A Google Apps Script function I wrote

I code in javascript all the time, but it’s usually stuff that only I would find useful. Additionally, the code is often too sloppy to make sense. But I cleaned up this function and I’m happy to share it with the world, in hopes that it might save other folks some time.

function CycleSheets()
If you’re like me, you’ve got too much data in your workbook and it’s maddeningly slow to scroll through all the sheets

If there are several sheets that you want to delete, it’s a pain to go navigate to each sheet and right click to delete and then confirm in an alert box.

So, this is a function that will show the user each sheet by name and size, giving them the choice as to whether or not to delete that particular sheet.
After all the sheets have been cycled through, the user will be shown a list of the sheets they’ve selected to delete, at which point they can confirm and delete or cancel.

function cycleSheets(){
   var ui       =   SpreadsheetApp.getUi();
   var ss       =   SpreadsheetApp.getActive();
   var sheets   =   ss.getSheets();
   var sheetCt  =   sheets.length;
   var delArr   =   [];
   
   for (var a = 0; a < sheetCt; a++){ var thisSheet = sheets[a];
      var thisCount =  a + 1;
      var sheetID   =  'Sheet ' + thisCount + '/' + sheetCt + ', named: ' + thisSheet.getName();
      var sheetData =  sheetID + '\nSheet contains ' + thisSheet.getLastRow() + ' rows.';
      var deleteYN  =  ui.alert('Delete this sheet?', sheetData, ui.ButtonSet.YES_NO_CANCEL);

      if (deleteYN  == ui.Button.YES)    {
         delArr.push(thisSheet); Logger.log('Sheet ' + thisCount + ' will be deleted.');
      }
      if (deleteYN  == ui.Button.NO)     {
         Logger.log('Sheet number ' + thisCount + ' will *not* deleted.');
      }
      if (deleteYN  == ui.Button.CANCEL) {
         var a = sheetCt + 1; ui.alert('Cancelled!'); return
      }
      if (deleteYN  == ui.Button.CLOSE)  {
         var a = sheetCt + 1; ui.alert('Closed and cancelled!'); return
      }
   }
   if (delArr.length > 0)   {
      var sheetNames   =   '';
      for (var b = 0; b < delArr.length; b++)   {
         var sheetNames = sheetNames + delArr[b].getName() + '\n'
      }
      var deleteConf   =    ui.alert('Are you sure you want to delete all of these sheets?', sheetNames, ui.ButtonSet.YES_NO);
      if (deleteConf   ==   ui.Button.YES){
         for (var c = 0; c < delArr.length; c++){
            ss.deleteSheet(delArr[c]);
         }
         ui.alert('You deleted ' + delArr.length + ' sheet(s). \nNow there are ' + ss.getSheets().length + ' sheets left.')
      }
      else {   
         ui.alert('You cancelled this deletion.')
      }
   }
   else {
      ui.alert('You didn\'t choose any sheets to delete!')
   }
}

Anyway, I hope you get some use out of it. Tweet me @skweeds to let me know how it works out!