Dimensional bidding custom algorithms

Dimensional bidding is a feature that allows you to apply one bid factor to a combination of target vector values. For example, you could apply a bid factor of 3.14 to the combination of mobile (device type) and Singapore (geo). If you targeted each of those vectors separately with a different bid factor, your two bid factors would multiply together. Multiplying multiple bid factors together to target precise, high-value market niches can result in unnecessarily high bids, which may result in the need for a max bid to act as a cap on your bid possibilities. To solve for this, you can extend your current ad group strategy with dimensional bidding to look for small pockets of high value while avoiding an exponential rise in bid value.

The following diagram illustrates the workflow process.

Data source configuration

Dimensional bidding is key performance indicator (KPI)-agnostic, so it doesn't matter whether you choose CTR, CPA, or something else as your goal. Dimensional bidding is represented as a list of item combinations, and each bid list is accessible at a selected level in your platform architecture. The code samples below assume an ad group level bid list.

This guide uses data from the REDS service as its input source, but you can use any data available to you to tailor your bidding strategy. The KPI you choose will determine the type of REDS events that you need to ingest.

CPA CTR CPC CPCV TVCR Viewability
Impressions * * * * * *
Conversions *
Clicks * * *
Video Events * * *

Considerations

To create a bid list with more than two dimensions, like the examples in this article, please contact your Account Manager.

Identifying and ingesting data

Once you've selected your data source and selected the appropriate ad groups, you will be able to begin ingesting and parsing data.

Data retrieval

Prior Knowledge Needed and Call-Outs

Below, you will find a Python code snippet that connects to the Amazon s3 API via a Boto package (2.x version) to retrieve two types of data events: impressions and conversions.

Each new data logs' rows are appended to a single CSV file, which occurs inside the function ingestGzipLogs.

The final result includes two master CSV data files:

You can reconfigure this code snippet to allow for more frequent retrievals of the data, as opposed to a one-day batch retrieval, depending on your individual needs.

3.1.1 ingest logs - Python code sample

def ingestGzipLogs(dateFilter):
    for key in mybucket.list(FILTER_PATH + str(dateFilter)):
        logName = key.name
        if "impressions" in logName:
            key = ReadOnce(key)
            gz_file = gzip.GzipFile(fileobj=key, mode='rb')
            for line in gz_file:
                newLine = line.decode('utf-8').split('\t')
                with open(impCSV, "a") as output:
                    writer = csv.writer(output)
                    writer.writerow(newLine)
        elif "clicks" in logName:
            key = ReadOnce(key)
            gz_file = gzip.GzipFile(fileobj=key, mode='rb')
            for line in gz_file:
                newLine = line.decode('utf-8').split('\t')
                with open(clicksCSV, "a") as output2:
                    writer = csv.writer(output2)
                    writer.writerow(newLine)
        else:
            continue

You will use the impression and click data to calculate CTR distributions at the ad group level for a particular dimensional bidding combination.

Analysis and data transformation

In this section, we'll be using the R language (with its quick prototyping advantages) to look at the main themes of performing data analysis and transformation.

Download r sample code to guide you through the data-transformation operations.

Reading CSV files

To start, you can use the fread function from the data.table library package (as a faster alternative to read.table or read.csv) to quickly read your two CSV files from the initial retrieval. You may use functions such as dim(impData) or summary(impData) to get a general understanding of the dimensions of the data set, basic statistics along each column, and whether there are missing values.

impData <- fread("./DA_impressions_2018-02-16.csv", header = FALSE)
colnames(impData) <- c("logentryTime", "impID", "partnerID", "advertiserID", "campaignID", "adgroupID", "privateContractID", "audienceID", "creativeID", "adformat", "frequency", "ssp", "publisherID", "dealID", "site", "referrerCatList", "foldPosition", "userHourOfWeek",
 "userAgent", "IPAddress", "TDID", "country", "region", "metro", "city", "deviceType", "OSFamily", "OS", "browser", "recency", "languageCode", "mediaCost", "feeFeatureCost", "dataUsageCost", "TTDCost", "partnerCost",
 "advertiserCost", "lat", "long", "deviceID", "zipcode", "processedTime", "deviceMake", "deviceModel", "renderingContext", "carrierID", "tempCelsius", "tempBucketStartCelsius", "tempBucketEndCelsius","placementID")
 #provide header for impressions data
clicksData <- fread("./CLICKS_DATA.csv", header = FALSE)
colnames(clicksData) <- c("logentryTime", "clickID", "IPAddress", "referrerURL", "redirectURL", "campaignID", "channelID", "advertiserID", "displayImpressionID", "keyword", "keywordID", "matchType", "distributionNetwork", "TDID", "rawURL", "processedTime", "deviceID")
#provide header for click data

The sample code above assumes that you want to produce a range of dimensional bidding combinations for every ad group under the partner. Of course, you do have the option to filter down to the appropriate subset of data.

The first major code snippet showcases the importance of choosing the most appropriate dimensional-bidding combination for your ad group. In this example, you may believe that the combination of SSP, publisher ID, site, and impression placement ID could be important. You can also experiment with different kinds of combinations in parallel to see which perform best.

subImpData <- impData2 %>% select(impID, advertiserID, campaignID, adgroupID, ssp, publisherID, site, placementID)
subImpData$SPSPID = paste(subImpData$ssp, subImpData$publisherID, subImpData$site, subImpData$placementID, sep="_")

Following your selection of specific combinations, you can then determine how to handle the calculation of the click-through rates (CTRs) for each combination (in this case, the combination of SSP, publisher, site, and impression placement ID). The CTR ratio requires the numerator count, or the number of times a combination has appeared for a specific campaign and ad group.

clickCountData <- joinedData %>% add_count(campaignID.x, adgroupID, SPSPID)
allCountData <- subImpData %>% add_count(campaignID, adgroupID)
colnames(clickCountData)[colnames(clickCountData)=="n"] <- "numeratorCTR"
colnames(allCountData)[colnames(allCountData)=="n"] <- "denominatorCTR"

Then, you can merge these datasets together to perform the CTR calculation.

joinedData2 <- merge(allCountData, clickCountData, by.x = 'impID', by.y = 'impID', all.x = TRUE)

Finally, using the "dplyr" package under R, you can string together a sequence of data transformations to finish the calculation.

transformedData <- joinedData2 %>% select(campaignID, adgroupID.x, SPSPID.x, numeratorCTR, denominatorCTR) %>% group_by(campaignID, adgroupID.x) %>% mutate(ctr = numeratorCTR/denominatorCTR)

If you wanted to create distributions of CTR values per campaign and ad group, you could calculate the mean CTR and standard deviation of that distribution.

transformedData2 <- transformedData %>% group_by(campaignID, adgroupID.x) %>% mutate(ctrThreshold = mean(ctr) + sd(ctr))

With the above calculations finished, you could create a mathematical threshold that equates to keeping any combination that is one standard deviation above the mean for relatively high performance. In other words, you are employing a rule-based model to affect what the ad group optimizations should look like. There is also an additional step in which a "sdFactor" is calculated that effectively becomes the bid factor for a specific combination. This represents how far the calculated CTR is above the determined threshold (in a ratio term). Please note that the 0.1 constant dampens the effect of the sdFactor because of high variability based on one day's worth of won impressions.

transformedData3 <- transformedData2 %>% group_by(campaignID, adgroupID.x) %>% mutate(sdFactor = 0.1*(ctr/ctrThreshold))
transformedData3 <- unique(transformedData3, by = c("campaignID", "adgroupID.x", "SPSPID.x"))
finalData <- transformedData3 %>% filter(ctr >= ctrThreshold & ctr > 0)

Activate

Now it is time to activate the outcomes, which are the sdFactors from the previous step, via the Dimensional Bidding API.

Format data into a JSON bid list

You can start by creating a subset on one ad group in the final dataset in R.

finalData[finalData$adgroupID.x =='ADGROUP_ID',][,3:8]

This results in the following:

The goal is to take the sdFactors and feed them into a dimensional-bidding bid list JSON structure, shown in the following request.

POST /v3/bidlist request

In this example, we are creating the BidList at the AdGroup level.

{
  "Name": "Bidlist 1",
  "BidListAdjustmentType": "Optimized",
  "ResolutionType": "ApplyMultiplyAdjustment",
  "BidLines": [
    {
      "BidAdjustment": 1.02,
      "DomainFragment": "com.strawdogstudios.simonscatcrunchtime",
      "SupplyVendorId": 99,
      "PublisherId": "FYBER-139222",
      "ImpressionPlacementValue": "530c4d2894e444a1976f41d527c03a07"
    },
    {
      "BidAdjustment": 1.02,
      "DomainFragment": "com.pandora.android",
      "SupplyVendorId": 50,
      "PublisherId": "71711271",
      "ImpressionPlacementValue": "cKClcPNTu1Te"
    },
    {
      "BidAdjustment": 1.02,
      "DomainFragment": "com.merriamwebster",
      "SupplyVendorId": 118,
      "PublisherId": "11653",
      "ImpressionPlacementValue": "177976"
    },
    {
      "BidAdjustment": 2.03,
      "DomainFragment": "com.cardgame.solitaire.flat",
      "SupplyVendorId": 1,
      "PublisherId": "f77344fc701f4b1f981238390e0b8ffb",
      "ImpressionPlacementValue": "f37ac1804f8040a4b49295e5d13167ff"
    }
  ],
  "BidListOwner": "AdGroup",
  "BidListOwnerId": "sberg7l"
}

POST /v3/bidlist response

{
    "IsGlobal": false,
    "BidListDimensions": [
        "HasDomainFragmentId",
        "HasSupplyVendorId",
        "HasImpressionPlacementId",
        "HasPublisherId"
    ],
    "BidListId": "2131712",
    "Name": "Bidlist 1",
    "BidListSource": "User",
    "BidListAdjustmentType": "Optimized",
    "ResolutionType": "ApplyMultiplyAdjustment",
    "BidLines": [
        {
            "BidLineId": "1216340",
            "BidAdjustment": 2.030000,
            "DomainFragment": "com.cardgame.solitaire.flat",
            "SupplyVendorId": 1,
            "PublisherId": "f77344fc701f4b1f981238390e0b8ffb",
            "ImpressionPlacementValue": "f37ac1804f8040a4b49295e5d13167ff"
        },
        {
            "BidLineId": "1216341",
            "BidAdjustment": 1.020000,
            "DomainFragment": "com.merriamwebster",
            "SupplyVendorId": 118,
            "PublisherId": "11653",
            "ImpressionPlacementValue": "177976"
        },
        {
            "BidLineId": "1216342",
            "BidAdjustment": 1.020000,
            "DomainFragment": "com.pandora.android",
            "SupplyVendorId": 50,
            "PublisherId": "71711271",
            "ImpressionPlacementValue": "cKClcPNTu1Te"
        },
        {
            "BidLineId": "1216343",
            "BidAdjustment": 1.020000,
            "DomainFragment": "com.strawdogstudios.simonscatcrunchtime",
            "SupplyVendorId": 99,
            "PublisherId": "FYBER-139222",
            "ImpressionPlacementValue": "530c4d2894e444a1976f41d527c03a07"
        }
    ],
    "BidListOwner": "AdGroup",
    "BidListOwnerId": "sberg7l",
    "IsAvailableForLibraryUse": false
}

Associate the new bid list to an ad group

The last step is to associate the BidListId with the AdGroup AssociatedBidLists so that the AdGroup uses the correct bid lists during live bidding.

Adding a bid list to an existing ad group

When using PUT /v3/adgroup to associate a new bid list to an existing ad group, it's important to check if there are any other bid lists already associated to that ad group. PUT /v3/adgroup replaces the contents of the AssociatedBidLists object with what is sent in the request.

When adding a BidList to an AdGroup that was previously created, use GET /v3/adgroup/{adGroupId} to gather the current state of all editable properties in the AdGroup before using PUT /v3/adgroup to send through the same information, appending the existing BidList to AssociatedBidLists.

IsDefaultForDimension for each AssociatedBidList should be sent with each PUT request. If the value is currently true and is not passed with the PUT request, it will revert back to the default of false and may cause unexpected behavior.

GET /v3/adgroup/{adGroupId} curl Request
curl --location --request GET 'https://api.thetradedesk.com/v3/adgroup/sberg7l' \
--header 'Content-Type: application/json' \
--header 'TTD-Auth: yourauthtoken' \
--data-raw ''
GET /v3/adgroup/{adGroupId} Response
{
    "CampaignId": "t0ncimu",
    "AdGroupId": "sberg7l",
    "AdGroupName": "2020-03-09 Ad Group Test",
    "Description": null,
    "IsEnabled": false,
    "IsHighFillRate": false,
    "AdGroupCategory":{
        "CategoryId": 8311
    },
    "RTBAttributes": {}, /*This object was collapsed to save page space.*/ 
    "Availability": "Available",
    "CreatedAtUTC": "2020-03-11T00:39:53.227",
    "LastUpdatedAtUTC": "2020-03-11T00:39:53.227",
    "AssociatedBidLists": [
        {
            "BidListId": "2131711",
            "IsEnabled": true,
            "IsDefaultForDimension": true,
            "BidListSource": "User",
            "BidListAdjustmentType": "Optimized",
            "BidListDimensions": [
                "HasGeoSegmentId"
            ]
        }
    ],
    "AvailableRecommendations": [],
    "CustomLabels": [],
    "AreFutureKoaFeaturesEnabled": false,
    "PredictiveClearingEnabled": false,
    "CurrentAndFutureAdditionalFeeCards": []
}
PUT /v3/adgroup Request
{
    "AdGroupId": "sberg7l",
	"AssociatedBidLists": [
        {
            "BidListId": "2131711",
            "IsEnabled": true,
            "IsDefaultForDimension": true,
            "BidListAdjustmentType": "Optimized",
        },
        {
            "BidListId": "2131712",
            "IsEnabled": true,
            "IsDefaultForDimension": true,
            "BidListAdjustmentType": "Optimized",
        }
    ]
}
PUT /v3/adgroup Response
{
    "CampaignId": "t0ncimu",
    "AdGroupId": "sberg7l",
    "AdGroupName": "2020-03-09 Ad Group Test",
    "Description": null,
    "IsEnabled": false,
    "IsHighFillRate": false,
    "AdGroupCategory":{
        "CategoryId": 8311
    },
    "RTBAttributes": {}, /*This object was collapsed to save page space.*/ 
    "Availability": "Available",
    "CreatedAtUTC": "2020-03-11T00:39:53.227",
    "LastUpdatedAtUTC": "2020-03-11T00:39:53.227",
    "AssociatedBidLists": [
        {
            "BidListId": "2131711",
            "IsEnabled": true,
            "IsDefaultForDimension": true,
            "BidListSource": "User",
            "BidListAdjustmentType": "Optimized",
            "BidListDimensions": [
                "HasGeoSegmentId"
            ]
        },
        {
            "BidListId": "2131712",
            "IsEnabled": true,
            "IsDefaultForDimension": true,
            "BidListSource": "User",
            "BidListAdjustmentType": "Optimized",
            "BidListDimensions": [
                "HasDomainFragmentId",
                "HasSupplyVendorId",
                "HasImpressionPlacementId",
                "HasPublisherId"
            ]
        }
    ],
    "AvailableRecommendations": [],
    "NewBidLists": [],
    "CustomLabels": [],
    "AreFutureKoaFeaturesEnabled": false,
    "PredictiveClearingEnabled": false,
    "CurrentAndFutureAdditionalFeeCards": []
}