Estimating Multiplicative Probabilities Using ANSI-SQL Windowing

SQL can be a quite efficient tool to implement particular types of statistics. Since windowing became a part of the ANSI standard, this holds for more and more databases as well as mathematical/structural complexities.

For example, product limits can be incrementally estimated by multiplying the probabilities over age. Only the quite impressive ANSI SQL aggregate function list  does not contain a corresponding operation.

Fortunately, we know (or at least Google knows 😉 that for positive P(i), it holds that ΠP(i) = EXP(Σln(P(i)))

This allows us to realize a (batch) product limit estimation including the determination of confidence intervals such as in the following example which has been generalized from Oracle to Teradata. Already included are some objects needed for the sequel post, just in case you wonder about the verbosity:

create table MATERIAL (
	material int primary key not null,
	description varchar(100)
);
insert into MATERIAL values (1, 'sit');
insert into MATERIAL values (2, 'aenean');
insert into MATERIAL values (3, 'adipiscing');
insert into MATERIAL values (4, 'nulla');
insert into MATERIAL values (5, 'in');
insert into MATERIAL values (6, 'sed');

create table PRODUCT (
	serial int primary key not null,
	material INT references MATERIAL(material) not null,
	manufacturing_date date not null
);

insert into PRODUCT  values (1, 2, '2002-08-14');
insert into PRODUCT  values (2, 1, '2001-02-20');
insert into PRODUCT  values (3, 1, '2005-10-27');
insert into PRODUCT  values (4, 1, '2003-10-23');
insert into PRODUCT  values (5, 1, '2001-12-21');
insert into PRODUCT  values (6, 2, '2010-10-11');
insert into PRODUCT  values (7, 1, '2002-06-28');
insert into PRODUCT  values (8, 2, '2009-12-29');
insert into PRODUCT  values (9, 1, '2002-01-10');
insert into PRODUCT  values (10, 2, '2008-10-19');
insert into PRODUCT  values (11, 3, '2007-01-04');
insert into PRODUCT  values (12, 3, '2008-08-06');
insert into PRODUCT  values (13, 4, '2010-08-01');
insert into PRODUCT  values (14, 4, '2011-12-01');
insert into PRODUCT  values (15, 5, '2012-02-03');

create table REPAIR (
	serial_flawed int unique references PRODUCT(serial) not null,
	repair_date date not null
);

insert into REPAIR values (7,'2004-01-22');
insert into REPAIR values (6,'2011-04-09');
insert into REPAIR values (2,'2001-06-21');
insert into REPAIR values (4,'2010-07-25');
insert into REPAIR values (1,'2011-06-16');
insert into REPAIR values (10,'2009-04-17');
insert into REPAIR values (5,'2003-04-13');
insert into REPAIR values (3,'2011-12-31');
insert into REPAIR values (9,'2002-06-10');
insert into REPAIR values (11,'2010-06-10');

/* Not all ANSI databases cope with my beloved nested CTEs, so here is the explicit version */ 

/* aggregating repair data by part age */ 
create view BASE_EXPERIMENT as
   select PRODUCT.material
        , REPAIR.repair_date-PRODUCT.manufacturing_date as age
        , cast(sum(1) as float) as samples
     from PRODUCT 
       join REPAIR on REPAIR.serial_flawed=PRODUCT.serial
    group by PRODUCT.material
           , REPAIR.repair_date-PRODUCT.manufacturing_date
;

/* building intervals and base statistics */
create view POPULATION as 
   select BASE_EXPERIMENT.*
        , row_number() over (partition by BASE_EXPERIMENT.material 
                             order by BASE_EXPERIMENT.age)
          as experiment
        , cast(
             sum(BASE_EXPERIMENT.samples) over (partition by BASE_EXPERIMENT.material 
                                                order by BASE_EXPERIMENT.age 
                                                rows between current row 
                                                   and unbounded following) 
          as float) as population
        , min(BASE_EXPERIMENT.age) over (partition by BASE_EXPERIMENT.material 
                                         order by BASE_EXPERIMENT.age
                                         rows between 1 following 
                                           and unbounded following) 
          as next_age
     from BASE_EXPERIMENT
;

/* Here is the actual estimation magic done */
create view SURVIVAL as 
   select POPULATION.*
        , case 
             when POPULATION.population<=1 then null 
             else 
                exp(
                 sum(
                   ln(
                     case 
                        when POPULATION.age is null then 1.0
                        when POPULATION.population=POPULATION.samples then null 
                        else 1.0-POPULATION.samples/POPULATION.population 
                     end
                   )
                 ) over (partition by POPULATION.material 
                         order by POPULATION.age 
                         rows between unbounded preceding 
                                  and current row 
                 )
                )
          end as probability
        , sqrt(
             sum(
                 case 
                    when POPULATION.experiment=1 then 0.0
                    when POPULATION.population>=POPULATION.samples 
                         then POPULATION.samples/
                              (POPULATION.population*(POPULATION.population-POPULATION.samples))
                    else null 
                 end
             ) over (partition by POPULATION.material 
                     order by POPULATION.age 
                     rows between unbounded preceding and 1 preceding 
             )
          ) as confidence
     from POPULATION
;

create table REPAIR_ESTIMATIONS as (
   select SURVIVAL.material
        , SURVIVAL.age
        , SURVIVAL.next_age
        , 1-coalesce(SURVIVAL.probability,0) as probability
        , 1-coalesce(least(SURVIVAL.probability+1.96*SURVIVAL.probability*SURVIVAL.confidence,1),1) as lower_bound_95
        , 1-coalesce(greatest(0,SURVIVAL.probability-1.96*SURVIVAL.probability*SURVIVAL.confidence),0) as upper_bound_95
        , 0 as depth
     from SURVIVAL  
) 
WITH DATA -- Teradata slang, remove for Oracle
;

/* Thats all folks ... until we speak of structured materials next time */
select * 
  from REPAIR_ESTIMATIONS 
 order by material
        , age
;

BTW, Mockaroo is a really nice sample data generation service … if only we could influence random distributions and impose constraints …

Advertisements

Bowdenzug an der FahrertĂŒr des Skoda Oktavia tauschen

Endlich mal wieder ein erfolgreiches Wochenendprojekt 😉

Auf dem !!Nachhauseweg vom  TĂŒV!! machte es vor Weihnachten beim Aussteigen plötzlich “ratsch”. Die FahrertĂŒr ließ sich nicht mehr von innen öffnen – zumindestens nicht auf wĂŒrdige Art und Weise und ohne eine lĂ€chelnde Zuschauerschaft auf dem Discounter-Parkplatz.

Bei der ersten Analyse fiel dann aus der Innenverkleidung sofort ein kleiner Haken mit einem Drahtrest raus, der darauf schliessen ließ, dass es sich dabei um die abgerissene Befestigung des Bowdenzugs handelte.

Bei Ebay wird man schnell fĂŒndig, wenngleich es kein Überangebot an solchen Ersatzteilen gibt. Der Austausch war dann etwas fuddelig, aber selbst mit begrenztem handwerklichen Talent ohne grössere KollateralschĂ€den durchfĂŒhrbar:

  1. Innenverkleidung des Aussenspiegels mit flacher Klinge zart lockern und abheben. Stecker lösen.
  2. Steuerung des Aussenspiegels am Innenöffner mit flacher Klinge zart lockern und heraushebeln. Stecker lösen. Kleine Schraube hinter dem Innenöffner lösen.
  3. Hintere Plastikverschalung am inneren TĂŒrgriff leicht in Richtung AutotĂŒr abwechselnd links und rechts hebeln und abziehen. Dadurch wird nun auch der Rest der Bedienkonsole frei, die man dann nach oben und vorne herausnehmen kann. Stecker abziehen.
  4. Jetzt die 8 Torque-Schrauben an Stirnseite (2 StĂŒck), Fussseite (2 StĂŒck) und unten (3 StĂŒck) lösen.
  5. Schliesslich die drei wesentlichen Kreuz-Schrauben in der verbliebenen Bedienkonsole lösen. Damit wird die Innenverkleidung nun frei.
  6. Wer die Innenverkleidung nicht komplett abreissen will, kann sie nun anheben und mit einem StÀnder, Stuhl o.À. einfach hochgeklappt lassen, um darunter zu arbeiten.
  7. Jetzt die Isolierfolie den alten Bowdenzug und ein paar Zentimeter am linken Schloß entlang mit einem scharfen Messer aufschneiden,  und aufklappen. Die Folie wird noch gebraucht, daher Vorsicht.
  8. Zwei Torque-Schrauben an der Stirnseite, die das Schloß halten, lösen.
  9. Das Schloß muss man nun nach rechts in Richtung der TĂŒröffnung drĂŒcken, bis die AufhĂ€ngung des Bowdenzugs sichtbar wird. Den alten Bowdenzug aus den Halterungen lösen und senkrecht nach unten halten, damit er aus der AufhĂ€ngung gezogen werden kann. Sollte das Schloß nicht genĂŒgend Spiel haben, kann man die Schraube des davor angebrachten Querblechs auch lockern.
  10. Selbes Spiel beim Einbau des neuen Bowdenzugs: Senkrecht halten, in die AufhÀngung einfÀdeln, dann in Zielposition bringen und wieder festhaken!
  11. Alles in umgekehrter Reihenfolge wieder zusammenbauen, Folie an der Schnittnaht wieder sauber zusammenkleben, da sonst Kondenswasser eintreten kann.

GMaps-API + Awesome Screenshot = High-Res Treasure Map

There is a certain positive correlation to observe between

  • the age of a child and
  • the thrill expected from its birthday party.

So last week, I found myself thinking about some kind of Ingress competition for my son’s 10th anniversary. With some intervention of my wife, fortunately, we came up with a more down-to-earth scheme: Two teams of five boys each (the “red knights” versus the “blue monks”)  had to hunt down 20 “magic symbols” (really horrifying tongue tattoos only to be “resolved by a truthful mouth”) which the “white sorcerer” had hidden in a rural area of ~20 square kilometers between our house and a playground/barbecue place in the village nearby.

From the two tasks of the white sorcerer

  • Hiding the treasures only equipped with a bike and a single hour of sparetime
  • Printing two maps on A3 paper detailed enough such that the young lads stay on track

only the latter turned out to be easy enough, thanks to the Google Maps Javascript API.

Treasure Map using Google Maps
Treasure Map using Google Maps

It allows to create a web page with a quite high resolution of

html { width: 4200px; height: 2970px }
body { height: 100%; width:100%; margin: 0; padding: 0 }

For leaving out any unnecessary distractions, you can customize the map options

var mapOptions = {
center: new google.maps.LatLng(49.405500,7.195000),
zoom: 17,
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.ROADMAP,
styles: [
{featureType: 'road',elementType: 'labels.text',stylers: [{visibility: 'off'}]},
{featureType: 'poi',elementType: 'all',stylers: [{visibility: 'off'}]}
]
};

var map = new google.maps.Map(document.getElementById(“map_canvas”),
mapOptions);

Then, you can place valuable hints on the resulting bitmap once you detected their GPS coordinates (e.g., by using the right-click command “What’s here” in the official Gmaps interface).

var start = new google.maps.Marker({
position: new google.maps.LatLng(49.406500,7.177241),
title:"Start",
icon: 'tower.png'
});
// To add the marker to the map, call setMap();
start.setMap(map);

Finally, you need to capture the single-bitmap-web-page at once, independently of your screen resolution. This is the domain of Awesome screenshot.  Believe me, I tested a whole lot of these extensions in Chrome, but that was the only “awesome” one that didn’t crash and produced a reasonable result (click on the image above).

Trading ORA-30926 for ORA-08006

Trading ORA-30926 for ORA-08006

This one has bitten me this morning when violating against golden rule #3 (‘Never change two not completely predictable things at a common target simultaneously at least unless you are in hazardous mood.’ An even more popular exemplification of that rule would be the slogan ‘Don’t drink & drive.’).

So I changed

  • the semantics of one source table of a merge statement (extract individual phrases out of a text field into multiple rows)
  • the partitioning of the target table (including the enablement row movement)

The result of the former change alone should have produced an ORA-30926 (‘unable to get a stable set of rows’) when some … hmm, say humanoid at least … managed to restate the same phrase over and over again in the source system.

In conjunction with row movement, however, the merge statement will instead issue an ORA-08006 (‘Someone deleted that row. It wasn’t me.’) which leaves one puzzled until one manages to find Todor Botevs helpful investigation.

 

 

 

Sampling consistently out of (unordered, splitted, fragmented) detail data

One of the problems when sampling data out of raw files is that there maybe consistency constraints on the picked lines.

hashrandomFor example, if you like to extract representative sales slips out of TLOG texts, you want all the positions belonging to one sales slip to be sampled as a whole … and you want that property consistently over all processes/machines onto which the texts/sampling has been distributed.

Using the typically seeded pseudo-random sequences will not work as expected, here. You would have to first aggregate the sales slip headers, sample on the level of headers and, again, join the resulting sample with the original TLOG data.

A nice idea that circumvents that necessity with only a minimal bit of overhead is inspired by Chuck Lam’s method of simply constructing Bloom-filter hash-functions. For each line, the pseudo-random generator is seeded with the hash-code of a given key/object. Then, a fixed position of the random sequence (the new “seed”) is read as the observed random value.

For our TLOG case, all the lines carrying the identical sales slip key will get the same random boolean computed and get filtered or passed through allthesame.

Gaggenau DF 240-261 Fehlercode E15

gaggenau_df_frontblende_befestigungenWer eine Gaggenau-Maschine besitzt, weiß um die nicht ganz so gĂŒnstigen Service-Bedingungen. Auch im Netz ist es mit Reparaturinformationen eher schlecht bestellt. Umso Ă€rgerlicher ist es, wenn statt einer Ladung gespĂŒltes Geschirr eine Fehlermeldung auftaucht, die Douglas Adams sicherlich zu weiteren “Per Anhalter”-BĂ€nden inspiriert hĂ€tte.

Offensichtlich gibt es aber baugleiche Bosch-Maschinen mit dem offenkundigen Hinweis auf Wasser in der Bodengruppe. Also flugs die Frontblende abmontiert (lediglich die 4 Schrauben im Bild halten die Blende, ansonsten ist sie lediglich eingehĂ€ngt; rausbohren der unteren Scharniere bringt nur Ärger!) und das Objekt des Anstosses (bspw. ein exakt auf den Pumpenablauf zugeschnittener Mandelsplitter) entfernt.

Hadoop Quick (Default) Port Reference & Help datanode startup on NTFS/Windows 8

Hadoop Quick (Default) Port Reference

This comes quite handy, e.g., when you need to setup firewall rules.

And here is a post related to the filesystem permission checks that the datanode service performs upon startup.

On my Windows 8 machine, I had to add the following lines to the hdfs-site.xml that comes with hortonworks hdp 1 distribution

<property>
 <name>dfs.datanode.data.dir.perm</name>
 <value>700</value>
 <description>The permissions that should be there on dfs.data.dir
 directories. The datanode will not come up if the permissions are
 different on existing dfs.data.dir directories. If the directories
 don't exist, they will be created with this permission.
 </description>
 </property>