Acquisition and traffic quality
Using IP clusters to investigate suspicious ad traffic
Which IPs behave like automated or pooled traffic, judged by how many geos, publishers, domains and user agents they touch?
Short answer
Trust: InferredProfile each IP by how widely it spreads (geos, publishers, domains, user agents), group similar IPs, then check which group carries your traffic and spend. This project stops halfway: it stores a cluster label per IP but never joins it to impressions, and cluster numbers change meaning between runs, so a label alone says nothing about risk.
Decision it supports: Which IP groups to exclude or review before paying publishers.
What to measure
IPs per cluster
Trust: Source-definedIP rows, grouped by class.
Defined as ip_model_2_clusters.count
COUNT(*)Trust: Translated
Show the SQL: IPs per cluster
COUNT(*)
Translated to MySQL SQL from ip_model_2_clusters.view.lookml L23-25. Not run against a warehouse.
Show the original LookML: ip_model_2_clusters.view.lookml L23-25
- dimension: rel_uas type: number sql: ${TABLE}.rel_uas - measure: count type: count detail: detail* sets: detail: null
advertising-marketing-small, ip_model_2_clusters.view.lookml, lines 23-25
IPs per label
Trust: Source-definedIP rows in the label-only view.
Defined as ip_2_cluster.count
COUNT(*)Trust: Translated
Show the SQL: IPs per label
COUNT(*)
Translated to MySQL SQL from ip_2_cluster.view.lookml L8-10. Not run against a warehouse.
Show the original LookML: ip_2_cluster.view.lookml L8-10
sql: ${TABLE}.class - dimension: ip sql: ${TABLE}.ip - measure: count type: count detail: detail* sets: detail: null
advertising-marketing-small, ip_2_cluster.view.lookml, lines 8-10
Impressions per cluster
Trust: MissingNeeds a join from cluster labels to impressions on ip. The project has none.
Which cluster is risky
Trust: InferredNot recorded. It has to be read from the average features per class.
The query
IPs per cluster
Trust: SuggestedHow many IPs fall in each class of the two-cluster run. It is as far as the project's own fields go; the profile and the traffic per cluster need the suggested SQL below.
Show the SQL: IPs per cluster
SELECT ip_model_2_clusters.class AS class, COUNT(*) AS countFROM ip_model_2_clustersGROUP BY 1ORDER BY class
- ip_model_2_clusters declares no sql_table_name, so it reads a table named ip_model_2_clusters.
Suggested query. Fields translated to MySQL SQL from odd_browser_and_os_analysis.model.lookml L10 and ip_model_2_clusters.view.lookml L3-5, L23-25. Not run against a warehouse.
The data behind it
Trust: Inferred- Entities
- IP, cluster run
- Grain
- One row per IP per clustering run.
- Time
- None. Runs are not dated.
- Key fields
ip_model_2_clusters.ipip_model_2_clusters.classip_model_2_clusters.geosip_model_2_clusters.pidsip_model_2_clusters.rel_uas
What can change the answer 3
Caution: Labels are not joined to traffic
Trust: Source-definedNo explore joins a cluster view to impressions, clicks or conversions.
What goes wrong: Cluster-level traffic or revenue is built by hand outside Looker, with no grain check.
Read the guide: CTR from separate tables
Source: odd_browser_and_os_analysis.model.lookml L8-12
- explore: click_interactions_by_ip_for_useable_campaigns- explore: conversions_by_ip_for_useable_campaigns- explore: geo_pid_facts- explore: ip_2_cluster- explore: ip_3_cluster- explore: ip_model_2_clusters- explore: ip_model_3_clusters- explore: other_browser_os_impressions- explore: useable_campaign_ids
advertising-marketing-small, odd_browser_and_os_analysis.model.lookml, lines 8-12
Caution: Class ids change between runs
Trust: InferredCluster numbers are arbitrary per run. Class 1 in the 3-cluster view has no relation to class 1 in the 2-cluster view.
Show the SQL: class
ip_3_cluster.class
Translated to MySQL SQL from ip_3_cluster.view.lookml L3-5. Not run against a warehouse.
Show the original LookML: ip_3_cluster.view.lookml L3-5
- view: ip_3_cluster fields: - dimension: class type: int sql: ${TABLE}.class - dimension: ip sql: ${TABLE}.ip - measure: count
advertising-marketing-small, ip_3_cluster.view.lookml, lines 3-5
Note: IP is an untyped string
Trust: InferredEvery view stores ip as a string. Formatting differences or IPv6 forms can silently break a future join.
Show the SQL: ip
other_browser_os_impressions.ip
Translated to MySQL SQL from other_browser_os_impressions.view.lookml L27-28. Not run against a warehouse.
Show the original LookML: other_browser_os_impressions.view.lookml L27-28
- dimension: domain_id type: int sql: ${TABLE}.domain_id - dimension: ip sql: ${TABLE}.ip - dimension: new_subid sql: ${TABLE}.new_subid - dimension: os
advertising-marketing-small, other_browser_os_impressions.view.lookml, lines 27-28
How the real implementation does it
ip cluster views
Trust: Source-definedThe model view carries the features per IP next to its class.
Source: ip_model_2_clusters.view.lookml L1-22
- view: ip_model_2_clusters fields: - dimension: class type: int sql: ${TABLE}.class - dimension: geos type: int sql: ${TABLE}.geos - dimension: ip sql: ${TABLE}.ip - dimension: pids type: int sql: ${TABLE}.pids - dimension: rel_domains type: number sql: ${TABLE}.rel_domains - dimension: rel_req type: number sql: ${TABLE}.rel_req - dimension: rel_uas type: number sql: ${TABLE}.rel_uas - measure: count type: count detail: detail*
advertising-marketing-small, ip_model_2_clusters.view.lookml, lines 1-22
ip cluster views
Trust: Source-definedThe label view keeps only IP and class, one per run.
Source: ip_2_cluster.view.lookml L1-10
- view: ip_2_cluster fields: - dimension: class type: int sql: ${TABLE}.class - dimension: ip sql: ${TABLE}.ip - measure: count type: count detail: detail* sets: detail: null
advertising-marketing-small, ip_2_cluster.view.lookml, lines 1-10
other_browser_os_impressions
Trust: InferredImpressions carry the same ip string, so a join is possible in principle.
Show the SQL: the impression ip
other_browser_os_impressions.ip
Translated to MySQL SQL from other_browser_os_impressions.view.lookml L27-28. Not run against a warehouse.
Show the original LookML: other_browser_os_impressions.view.lookml L27-28
- dimension: domain_id type: int sql: ${TABLE}.domain_id - dimension: ip sql: ${TABLE}.ip - dimension: new_subid sql: ${TABLE}.new_subid - dimension: os
advertising-marketing-small, other_browser_os_impressions.view.lookml, lines 27-28
Other ways to measure it
Profile each cluster
Trust: SuggestedAverage the features per class so you can tell which cluster spreads across many geos, publishers and user agents.
Show the suggested SQL: cluster profile
SELECT class, COUNT(*) AS ips, AVG(geos) AS avg_geos, AVG(pids) AS avg_publishers, AVG(rel_domains) AS avg_rel_domains, AVG(rel_uas) AS avg_rel_user_agentsFROM ip_model_2_clustersGROUP BY class
Join labels to the traffic
Trust: SuggestedAttach one clustering run to impressions so traffic and bids can be grouped by cluster. It assumes one row per IP in that run; in LookML the same join is a many_to_one on ip.
Show the suggested SQL: impressions and bids per cluster
SELECT c.class, COUNT(*) AS impressions, SUM(i.bid_amount) AS bidsFROM other_browser_os_impressions AS iJOIN ip_model_2_clusters AS c ON c.ip = i.ipGROUP BY c.class
Name the clusters
Trust: SuggestedStore a label per class and run, such as suspicious and normal, in a small mapping table, and join it to the label views.
