Ok

En poursuivant votre navigation sur ce site, vous acceptez l'utilisation de cookies. Ces derniers assurent le bon fonctionnement de nos services. En savoir plus.

  • Les Fonctions (Trois)

    Une nouvelle notation

    On va ici définir une nouvelle notation de la mort qui tue pour les fonctions. Un langage plus simple qu'Haskell... 

    D'abord types et valeurs c'est pareil, et constructeurs de type et fonctions c'est pareil. 

    Une fonction c'est d'abord un "matcheur" qui déstructure suivant ses besoins une donnée d'entrée. 

    f: Int -> Int = inc = (x) (x+1) 

    Mieux, x étant argument "par défaut".

    f:Int->Int = x + 1 

    Inutile de noter "lambda" quelquechose qui est DEJA typé ici.

     

    Un constructeur de type, c'est pareil: 

    Option: *->* = (T) (None, T ) // ici, "virgule" veut dire "ou"

    Option: *->* = None, T

    On a donc unifié valeur, fonction, type... 

    Pour appliquer une fonction, on adjoint symbole fonctionnel et valeur: (inc 3) == 4

    Pour  appliquer un constructeur de type, pareil.

       a: (Option Int)   fait de la valeur a une option, c'est à dire une valeur taggée par le fait d'être une option.

     

    Prenons alors les 4 monades principales (la grande tétrade) et analysons les en détails, pour qu'elles forment le socle de l'évidence fonctionnelle, ce qui  manque pour VRAIMENT l'épouser et la comprendre. 

    On rapellera que les 3 interdits du fonctionnel, (interdiction de la valeur nulle, interdiction de la lecture, interdiction de l'écriture) seront couverts ici par les 3 patterns fondamentaux qui les prennent en compte: comment typer la valeur nulle, la lecture et l'écriture et mieux comment typer la lecture ET l'écriture simultanée.

    La monade Option

    On va ici s'affranchir des valeurs nulles, explicitement typées par la valeur "None" 

    Option: T->T = None, T // ici, "virgule" veut dire "ou"

    A partir de là: 

    Option.map : (Option T ) (  T->T')    ->    Option T' =

    (x  y) (             if (x == None) None else (      x== (Option a)     (       (y then Option) a     )                       )

    "x" est le premier argument, directement la valeur encapsulée par le premier argument de type Option T et y le deuxième, ce qui fait que le couple de déclaration de paramètre, "(x,y)" en début de notation, est en fait inutile...

    La notation peut aussi utiliser ici un opérateur  de "parallélisme logique" autour de la virgule/"ou". 

    Option.map = (None  ,   (Option a) )  (None,   ((y then Option) a)   )

    "Option" sera ici, aussi, une fonction, disons ce que fait Some en Scala... (le "run", ou "point" des monades).

    La notation rend la structuration/destructuration implicite, le type servant de gabarit, de traitement terminal. 

    En Scala, on ferait :  

    def map(x:Option[T], f: T =>T'):Option[T'] =

    x match { case None => None; case Some(x) => Some(f(x))}

    = Some(f(x.getOrElse (return None)))  // vla du scala hard mais qui marche.

    L'expression de flatMap est exactement la même... 

    Option.flatMap : Option T, T->Option[T'] =

    if (x==None) None else  (x then y )

    ==  (None,x) (None,x then y)

    Ou bien (x y) ( (None, Option(a)) y) (None, (y a) )

    En Scala:

    def flatMap(x:Option[T], f: T =>Option[T']):Option[T'] =

    x match { case None => None; case Some(x)=> f(x)}

     

    La monade Reader 

    Il s'agit de modéliser la lecture pure.

    Reader C : *->* = C -> T  // la valeur est une fonction de C, la configuration , vers le  type encapsulé T.

    Reader.map : Reader C T, T -> T' = x then y  // comment faire plus simple ? implicitement... 

    Ici, parce que typée à destination de Reader C T , y  (une fonction de T  vers T') est automatiquement composée avec une transformation vers Reader C T'... L'exécuteur de mon langage est vraiment astucieux et on pourrait être plus explicite. (Reader C T) pourrait être ainsi considéré explicitement comme un constructeur de valeur typée, avec comme argument la fonction qui le définit. 

    Reader.map : (Reader C T)  ( T->T' )  =   (Reader C T') ( x then y)    

    cela car (x then y ) est bien une fonction de C vers T'.

     

    Reader.flatMap :  Reader C T, T -> Reader C T' =

    (c) (( (x then y) c) c) 

    def flatMap (

       x:Reader[C,T],

       y: T =>Reader[C,T']):Reader[C,T']=

    Reader[C,T'] (c=> y(x.value(c)).value(c))

     

    Reader est donc une construction utilisable pour programmer. L'idée est de retarder à l'extrême l'emploi de la configuration (de type C). Un "Reader" c'est une accumulation de calculs en fonction d'une valeur inconnue, en fait une fonction en attente de son premier argument. La composition lambda serait: 

    f = for ( 

    a<- (Reader Int Int) (+3)

    b <- (Reader Int Int) (-2) 

    ) (a + b) 

    f est de type (Reader Int Int) et doit être appelé (avec pour argument une "configuration" ) pour donner quelque chose. Ici "1000" est la configuration. 

    f 1000 

    donne (1000 + 3) + (1000 - 2) = 2001

    Ainsi, la fonction d'un Reader fait office de calcul relatif par rapport à une valeur convoyé -à l'identique- par les flatMap lors d'une composition. 

    On a ici la plus parfaite illustration de la fonction, du rôle et du service rendu par une monade: le transport transparent dans une composition (nécessairement faite par flatMap, y a que ça pour ça) d'une information particulière, ici la configuration. L'abstraction de cette valeur qu'on peut , dans le cas du Reader, donner après coup est le cas d'usage particulier de la monade. Ici, on a une sorte d'interprétation "retardée": les calculs de la configuration ne seront fait qu'APRES le choix et l'application de la configuration.

    La monade Writer

    La monade Writer est un peu l'"inverse" du Reader. Elle va être utilisée pour convoyer par flatMap le texte d'un log, modifié par ajout à chaque étape. 

    Writer S : *->* = (S,T) 

    Writer.map : ( Writer S T)  (T -> T') = 

    x then ((z, w) ( z, y w))   // simple application of the function

    Writer.flatMap : ( Writer S T) , (T -> Writer S T') =

    x then ((z, w) (   (y w)  then  (p q) ( (p + z, q )  // add the logs... 

     

    Et donc, 

    f = for (

    a<- Writer ("first line,", 33)

    b<- Writer("second line", a * 2) 

    )  (a + b) 

    sera un Writer contenant la valeur 66 et le log "first line,second line". 

    Ici, la valeur convoyée est stockée au fure et à mesure dans l'objet par le flatMap. Pas d'abstraction finale, mais l'effet est réel. Notons que ici c'est le premier Writer de la chaine de flatMap qui initie le stockage. On évite, stylistiquement, de convoyer une instance particulière dans la suite de calculs, la simple référence au "type" permettant de connecter les différents Writer entre eux  dans la chaine de flatMap exécutés et la transmission progressive de la chaine stockée, augmentée à chaque étape. 

    Cela donne une capacité d'abstraction, les appels à Writer fait ici pouvant être remplacés par des appels à des fonctions quelconques retournant le Writer adapté. On évite ainsi une réification avec une instance et donc une affectation (berk berk). 

    La monade State

    La monade State fait tout, lecture ET écriture. 

    State : *->* = S -> (S,T)

    Ici on adoptera la notation "avec constructeur" du type de l'objet monadique. 

    State.map : (State S T)  (T ->T' )  =

    (State S T') (   x    then     ( (z w) (z, w then y) )      ) 

    State.flatMap : (State S T)  ( T -> (State S T') ) =

    (State S T') (  x then  (  (z w) (    (y w)(z)     )   ))

    La fonction encapsulée par la monade peut changer l'état, et c'est toute l'histoire.

    t = for (

    a<- State (  (x + 1, x+2)  ) //  

    b <- State(  (x+3, x+2)  ) // 

    ) (a + b) 

    t est une monade Reader prête à être évaluée, comme Reader, elle est un stockeur d'expressions.

    (t 1) = ( (1 + 1) + 3)  ,   2+2)

    Son "état" final sera 5 et la valeur finale calculée 4, obtenue en fonction de l'état précédent (2).

     

    On remarquera qu'une monade est en fait ici une fonction, disons que la valeur qu'elle encapsule est ici une fonction, c'est une monade "fonctionnelle" et que DONC, on peut et doit l'évaluer. 

    Reader et State, comme monades "lisibles" ont besoin qu'on leur passe une valeur extérieure pour fonctionner. C'est ce qui les rend compliquées, alors qu'en fait on s'y fait très bien. 

    Apprendre tout ça par coeur est un MUST  absolu. Merci qui? 

     

  • Les Fonctions (Deux)

    On avait parlé des fonctions, mais mal. 

    On recommence avec un autre point de vue, orienté vers autre chose et qui reste néanmoins dans l'axe de ce qu'on disait, et qui est qu'on doit aller vers une abstraction supplémentaire. Haskell, ou sa version perso est utilisé.

    Valeur

    On commence par le bas. On a des valeurs, ou objets abstraits de base. 

    Pour commencer la montée gentiment, on doit admettre qu'il y a différentes sortes de valeurs. Cela nous introduit aux "types". On a au moins les nombres, et il y a différent types de nombres, les chaines de caractères et les booléens ou bits qui prennent deux valeurs. Ca fait 3 types de valeurs simples au minimum.

    Type

    La première abstraction est donc le "type", rassemblement de valeurs possibles avec une identité. Il y a plusieurs sortes de types, et au combien. Pour définir des types, on va les combiner et la première forme du langage qu'on est en train de définir va consister à noter des "compositions" de types. 

    A = Int String 

    va être le type des tuples formés d'entiers et de chaines de caractères (String). On concatène un entier et une String. La conjonction, le "et". 

    A = Int, String

    va être le type des valeurs qui sont soit un entier soit une String. L'un ou l'autre, la disjonction, le "ou". 

    Fonction

    On passera sur l'"opération", équivalent de la valeur par sa simplicité nécessaire et qui permet de faire correspondre des valeurs à une autre. On abstrait tout ça en une fonction, définie plus précisément sur des types et à destination d'un type. 

    On va alors ainsi passer aux fonctions, êtres complexes, autres sortes de valeurs, et définies en deux étapes: par leur types, et par leur comportement sur les différents composants de leurs types d'entrée. 

    Type paramétré 

    Pour définir des types, on va utiliser un autre moyen, le paramétrage. Cela permettra de définir des sortes de fonctions sur des types en tant que tels et pas simplement en tant qu'ensembles de valeurs... héhé. Cela permettra ainsi et aussi de "transformer" des types. On va voir. 

    L'archétype du type transformé est l'"option". En gros on ajoute un élément à n'importe quel type existant. Cet élément additionnel, on va l'appeler "None", alors qu'on aurait pu l'appeler "something else" ou n'importe quoi d'autre, il est "en plus". 

    Option A = none, Some A 

    On aurait pu dire "Option A = none, A ", pour indiquer une fonction de A vers A "plus" l'ensemble formé de l'élément "none".  En fait c'est une question de syntaxe de langage. Un élément de "Option A", quand il n'est pas "none", sera noté "Some a", avec a élément de A. 

    En gros, "a" élément de A ne peut pas être noté pareil que "a", élément de "Option A"... Même si on pourrait, en fait. Il suffirait de faire le malin avec une expression contextuelle alambiquée, avec des lettres hébraïques en exposant... 

    Le paramétrage de type est en tout cas bien une abstraction différente de celle de la fonction, même si cela lui ressemble bigrement. "Option A" est bien un nouveau type, même si il n'est "que" l'ensemble de toutes les valeurs de toutes les applications sur A de toutes les fonctions possibles de A vers l'union de A et de none...

    De ce point de vue, un type paramétré définit ainsi un ensemble de fonctions. Et dans la mesure ou une fonction représente une opération, un ensemble d'opérations, donc,  un type paramétré est un "type de calcul". Chaque calcul de l'ensemble ayant un résultat dans l'ensemble ainsi défini. On a abstrait ici l'opération, le calcul lui même...

    On généralise immédiatement à la véritable abstraction et qui est l'ensemble pour tout type B, des fonctions de B vers Option A , et qui matérialise véritablement un calcul général, associant non pas A à B, comme le ferait une simple fonction bébête, mais Option A à B. Bien sur Option A est en fait un type, qu'on pourrait qualifier d'ordinaire, simplement ce qu'on veut dire avec mauvaise foi ici, c'est que le choix de ce type paramétré là caractérise le calcul exprimé par les fonctions en question sur le type paramètre: on a ici un calcul produisant un A qui s'exprime avec une caractéristique nouvelle, représenté par le type paramétré par A: il peut, c'est pour ça qu'on fait tout ça, "échouer". 

    Les fonctions du genre "B -> Option A" peuvent ainsi retourner none, et donc convoyer une sémantique d'"échec", ou d'"absence". On a réinventé le "pointeur Null". 

    On a ainsi une sémantique (une signification) pour un typage de fonction qui serait "intelligent". On abstrait, on représente une signification supplémentaire associée à un sous ensemble de l'espace global des fonctions. C'est pour cela que la définition des ensembles supports d'une fonction ne suffisent pas pour "genrer" précisément une fonction. Une fonction peut aussi appartenir au type de fonction défini par un domaine de destination "du type" "Option X". Bref, les types de fonction c'est pas simple... 

    Il y a bien sur d'autres types paramétrés... Autant qu'il y a de "types" de calcul. Cette adjonction là qui caractérise des ensembles variés de calculs qui se ressemblent s'appelle l'"effet". Un "effet" c'est ce qui type partiellement un calcul, et qui s'exprime par une transformation particulière apportée à un type. Pour enfoncer le clou, on veut dire que "Option A" c'est une modification de A, et pas simplement un type quelconque issu de A. 

    La composition 

    On connaissait la composition classique des fonctions. Les fonctions, ça se compose, merci. 

    L'opération de composition dit "rond" : f o g = h , h(x) = f(g(x)) , comme "combinateur" de fonctions est le parangon des combinateurs, des fonctions sur fonctions. Au fait, on parle bien de programmation "fonctionnelle": les valeurs auxquelles on s'intéresse le plus, ce sont les fonctions. Et donc on cherche le "méta", le "combinateur".

    Quand est il des fonctions vers des types à effet ? Et bien on peut pas. C'est tout le problème. Car une fonction est totale, elle prend et ne prend que ça, que les éléments de son type d'entrée. 

    A -> Option B

    B -> Option C 

    parangons d'opérations "typées" par leur destination, ne sont pas composables simplement, point final. 

    On aimerait donc une sorte d'opération, de "méta opération" sur fonctions, un combinateur... Cette sorte de combinateur, associé à l'effet permettrait de faire plusieurs choses: d'abord composer bien sur: 

    X : (A -> Option B , B -> Option C ) -> A -> Option C

    ensuite, si possible, encapsuler automatiquement l'effet et savoir quoi faire quand ses particuliarités se manifestent. Cela ferait une abstraction composable et aussi "additionnable". On profite pour faire la même chose aux choses qui se ressemblent. Ainsi, quand la fonction sur Option B  retourne un "None", et bien cet effet là  pourrait opportunément se propager tout seul comme un  grand et donner directement la valeur None à la composition. 

    Evidemment, on pourrait imaginer des compositions qui ne supposerait pas comme suggéré de donner None comme résultat à toute application d'une fonction à destination de "Option X". Mais cela serait arbitraire, et tordu. Après tout, None est fait pour ça: modéliser l'échec, le trou noir, le nul. Et quand on a échoué, on a échoué. Exit.

    Les classes de type

    On va alors introduire l'abstraction inventée pour le langage Haskell pour regrouper les types et les améliorer sans y toucher: les "classes" de types. Alors qu'il s'agit programmatiquement d'une alternative complète à ce qu'on appelle l'"orienté objet"et même d'une technique supérieure de modularisation et de modélisation, les mêmes mots sont employés (classe, instance) et avec des significations comparables, mais radicalement autres. 

    Une classe de type, c'est (déclarativement) tous les types pour qui existe un certaine ensemble de fonctions qui la définisse. Par exemple, on y va tout de suite, les types paramétrés M pour qui sont définis les fonctions "return" et "flatMap" dont les signatures respectives sont:

    return : A ->  M A

    flatMap: (A , A -> M B) -> M B

    définiront des types à effets composables (ceux dont on parlait plus haut). On les appellera les "monades". 

    Et hop, d'un coup, on a défini les classes de types, les compositions d'effets et les monades. L'essence du FP. 

    De fait on a été un peu rapide. La première des classes de types, c'est Eq, qui fournit l'égalité aux objets. Pas mal, non?  Avec en plus la capacité de contrôler les types des éléments qu'on compare (ils doivent être les mêmes) et de la définir récursivement en fonction des types composites à comparer. Le reste à l'avenant, les classes de type s'étendent et un type peut ainsi être défini avec toutes les classes de types qu'il implémente. On modularise la notion de type, qui se trouve donc être composable, et capable d'agréger tout espèce de comportement.

    Une alternative complète et puissante, en fait bien plus puissante que, à l'orienté objet traditionnel. La grande différence est que l'ajout, le comportement associé à la classe de type est d'emblée un module de comportement applicable à tout type, une sorte d'interface, comparable au trait Scala. Par contre, sémantiquement la classe de type est absolument intrusive, les types qu'elle commande sont soumis au contrat qu'elle transporte et impose, elle les définit, elle ne les fournit pas ! 

    Les fonctions

    Revenons aux fonctions. Elles ont bien des propriétés qui en font à la fois des éléments de choix pour programmer, mais aussi qui les rendent profondément (et paradoxalement) inaptes à cela. Précisons les grandes propriétés de ce qu'est la définition d'une fonction, en l'occurrence une "expression" forme syntaxique d'une reformulation avec des opérations d'une valeur issue de l'ensemble de départ, le domaine de la fonction. 

    Ce n'est que cela, et à condition de n'utiliser comme opération que des fonctions au même sens, dans certains cas primitives de manière ultime, on a ce qu'on appelle la "transparence référentielle" qui fait que tout appel de fonction peut être remplacée par l'application de sa définition à ses paramètres sans changer le sens global du programme. La fonction ne fait qu'abstraire. 

    La transparence référentielle s'applique ainsi aux programmes construit avec des fonctions "pures", c'est à dire RT pour leurs arguments RT. Cette "pureté" typique du fonctionnel, s'applique et s'exprime avec plus de détails selon les 3 modalités suivantes:

    - pas de valeur nulle : une fonction est totale, soit exclusivement consommatrice et productrice de valeurs typées. 

    - pas de lectures extérieures: à toute entrée une seule sortie, pas de demande cachée de lecture d'autres entrées.

    - pas d'écritures extérieures: la seule sortie est la valeur de retour, pas d'effets de bords invisibles.

    Cette pureté fait du fonctionnel une technique inutilisable dans son principe pour communiquer avec l'extérieur. Sauf si, et c'est la suite de l'histoire.  

    Les effets 

    On se permettra alors de sauter par dessus toute l'histoire récente des monades. La notion d'"effet" fut d'abord utilisée pour régler le premier problème. Il est parfaitement possible de se passer complètement de la valeur nulle, et la monade "option" est faite pour cela, utilisable et utilisée (par les happy few). 

    Pour les IOs, on (je parle d'Haskell) inventa un type spécial paramétré nommé "IO", qu'il suffisait aux fonctions de retourner. A partir de là, une fonction spéciale dite "main" à qui on passait une telle fonction se chargeait de procéder aux entrés sorties effectives en combinant les effets, devenus "effets de bords" mais contrôlés et exclusivement contenus dans l'exécution de fonction "main" à travers la monade IO.  

    Y a pas que les monades

    Les monades, du moins leur utilisation, ne sont pas "issues de la théorie des catégories". Elles sont générées par des besoins concrets exprimés par les programmeurs et se rattachent aux maths par la bande en fait... Et puis il y a les "arrows" (1) qui se manifestent, et sont utilisées pour  améliorer certaines implémentations. Tout continue bien de partir de préoccupations locales. 

    Les étapes de calcul

    Pour finir il y a bien une sorte de valeur qui nous échappe encore: le "statement", l'étape élémentaire de calcul, l'étape de calcul. Le corps de la définition d'une fonction est typiquement faite d'un enchainement de ces étapes, séquencées, testées, répétées. Au point que programmer consiste  à combiner ces étapes, voire à les définir, puis à les combiner. 

    On en vient alors à vouloir définir ce qu'il y a à faire comme des combinaisons, non pas d'appels de fonctions et d'expressions variées sur des données, mais de fonctions elles mêmes, les ensembles de données variées à considérer étant rendus invisibles. 

    Par exemple, l'addition de n sempiternellement exprimée comme une fonction à deux paramètres, dont évidemment le point d'entrée: addn = x, n: x + n , pourrait se décrire aussi comme addn = f , n : f (andThen inc) * n  . 

    Bref, une algèbre d'opérateurs, qui combinerait directement les opérations considérées comme des valeurs. 

    Un tel style de programmation est à portée et se trouve peu ou prou ce qu'offre Haskell, même si conceptuellement le pas ne semble pas franchi. On peut pourtant abstraire l'étape de calcul et directement passer à  l'après calcul, le calcul du calcul, donc.   

    Le principe est de se ramener à IO, qui va devenir le type de la valeur qui représente le "statement". Ce type, pratiquement d'usage universel, sera le codomaine (le type de destination) de toute fonction fabriquant une opération de calcul élémentaire. 

     

    (1) On peut lire au moins le début de : http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf 

  • Les bayésiens

    La question de la probabilité a deux grandes interprétations, dont la première, celle basée sur l'émergence d'une ratio hors d'un grand nombre de cas dont les fréquences relatives finissent sous l'effet du nombre par devenir absolues et suivent des règles intangibles promues au rang de lois, domine et fait l'examen des expériences scientifiques.

    La seconde, est elle basée sur l'incertitude chiffrée d'un examinateur subjectif regardant quelques cas qui confirment sans preuves véritables l'incertain... Les bayésiens sont des lascars obstinés et continuent de fasciner, voire d'opérer et cela partout. 

    On en vient au probabilités qui font l'objet de formalisations mathématiques variées, mais on ne peut se départir de chercher des formes intuitives à ce qui continue de fasciner pour des raisons évidentes: la prédiction de l'avenir reste l'incontournable forme du coté attirant du savoir: comment ? Vous sauriez le futur ? Et oui, car je connais le passé et ses lois...

    Pour commencer et pour mettre les choses au clair, la polémique entre bayésiens et fréquentistes a ainsi DEUX aspects qui n'ont rien à voir en apparence, alors que. 

    Le premier est la divergence sur l'interprétation de la notion même de probabilité: mesure de la confiance ou limite d'une fréquence de mesure répétée ?

    Le second est la la divergence sur la manière dont on calcule, les bayésiens partant d'une situation "a priori" que les fréquentistes contestent, cette situation à priori étant justifiée par l'interprétation bayésienne de la probabilité...

    Le Sida

    Connaissant le caractère mental du sida, on explorera le cas de base, celui du test d'hypothèse, en partant d'une prévalence du Sida de 1% dans population et d'un test rapide efficace à 99% dans ses deux caractères de "sensibilité" (pourcentage des sujets infectés dont le test est positif) et de "spécificité" (pourcentage de sujets non infectées dont le test est négatif). 

    On remarquera l'utilisation du mot "sujet", alors qu'il s'agit bien sur d'objets, personnes innocentes à laisser tranquille ou à plaindre d'une mort atroce.

    A partir de là on affirmera que si la prévalence du mal est (dix moins "q"= 10^-q)  et la précision du test (1 - dix moins "s" = 1 - 10^-s) , et bien la probabilité d'être atteint quand le test est positif sera :

    1  / 1 + 10^(q - s)  

    ce qui fait  exactement 1/2 dans le cas explicité ici où q = s = 2. 

    La démonstration

    On utilisera un arbre de manière à établir le plus vite possible les 4 probabilités des 4 cas. 2 modalités (oui/non on a le sida ou non, et ok/nok, le test est positif ou non). Cela doit être calculé en fonction des données données, et interprété suivant le sens des mots.

    Par exemple ici oui = 10^-q,  quand q=2, 1%. 

    Le point important, en fait fondamental, dans les probabilités croisées, est d'établir les grandes régions aggrégantes par exemple la région "oui", divisée par les régions "ok" et "nok", la question posée étant celle de la probabilité dite conditionnelle suivante: "oui sachant ok", c'est à dire la division par la probabilité de ok de la probabilité d'être à la fois oui et ok. Les grandes régions sont ainsi bien sur oui, ok , non et nok elles-mêmes interpénétrées mutuellement. 

    On en vient à la première définition "bayésienne": la probabilité de "A sachant B" est : P(A/B) = P (A^B) : P(B) 

    qui définit la probabilité dite conditionnelle, le symbole de la division étant : et le "^" exprimant bien sur l'intersection, ou le "à la fois", qui bien sur n'est en aucun cas une multiplication des probabilités correspondantes. 

    On en déduit d'ailleurs immédiatement le fameux théorème qui n'en est pas un, d'ailleurs, étant immédiatement issu de la définition de la probabilité conditionnelle. La probabilité de A^B ("A inter B") étant évidemment égale à

    P(A/B)*P(B) et à P(B/A)*P(A) par conséquent égaux eux-mêmes et donc: 

    1200px-Bayes'_Theorem_MMB_01.jpg

    On passera donc immédiatement sur cette histoire de "théorème" parfaitement fétichisée et sans aucun intérêt d'ailleurs, sinon le fait qu'il affirme simultanément deux fois un bête définition pour une expression linguistique elle dotée de sens, le fameux "a sachant b" qui apparait fréquemment dans les questions et les réponses des vrais problèmes. On a donc un contresens (un théorème qui n'en est pas un) pour une signification profonde (on exprime des vérités dotées de sens à travers des formes calculatoires non totalement triviales). 

    On ajoutera aussi aux "trucs" de Bayes la "décomposition" ultra utile et également directement issue de la définition de la conditionnalité:  X = P(X/A).P(A) + P(X/B).P(B)  

    Ainsi, pour répondre à la question fondamentale et terrible "ai-je le sida quand je suis positif ? ", on doit calculer une probabilité conditionnelle en en connaissant deux. 

    La première, la question angoissée est "oui sachant ok".

    Les deux données sont ce qui caractérise le test : la sensibilité,  soit "ok sachant oui" l'inverse de la question, et la spécificité "nok sachant non". On les supposera égales. Soit S= 10^-s et Q = 10^-q

    ok/oui = nok/non = 1 - S = 1 - ok/non , ok/non  = S

    On en vient alors à la question posée. 

    oui/ok = ok/oui . oui : ok 

    Or,  ok = ok/oui.oui + ok/non.non , comme on l'a vu, c'est ce que j'ai appelé la "décomposition de Bayes". 

    Donc: 

    1 : oui/ok = 1 + ok/non.non : ok/oui . oui   

    =  1 +   S (1- Q) / (1 - S) Q  

    = 1 + 10^q-s   (1 - 10^-q)/ 1 - 10^-s)  

    Ce qu'il fallait démontrer, le facteur correctif valant 1 en gros dans la plupart des cas. 

    Si s = 2 et q 4  (prévalance de 1 pour 10 000, soit 0,01%) 

    resultat = 1 : (1 + 10^4-2 (.9999/.99))  = 1 : 101 * (1.01) = 0,01% 

    Plus la prévalence est faible, disons inférieure à la sensibilité du test, moins le résultat est inquiétant... En fait et c'est la grande leçon, il faut pour que le test prévoie plus qu'une possibilité non nulle, que sa sensibilité soit supérieure à la prévalence...

    Ainsi un test sensible positif peut ne PAS entrainer d'inférence statistique en faveur de l'hypothèse qu'il teste. Cette erreur est l'"erreur du taux de base négligé". 

    Q de Yule

    Au fait si on examine les 4 cas possibles, sujet atteint (V ou F) et test positif ou non (P ou N), A, B, C, D avec 

    A=PV, B=PF, C=NV, D=NF, le coefficient Q de Yule va désigner l'efficacité du test  Q = AD-BC/AD+BC

    Le bayésianisme

    C'est alors qu'on en vient à l'interprétation. Que signifie ces chiffres, se disant être des "probabilités" ? Des probabilités de "quoi" ? On a deux théories. 

    La première est qu'il s'agit de la probabilité d'un événement considéré comme une mesure de sa fréquence d'apparition, la seconde est qu'il s'agit d'une mesure de la croyance en sa réalisation.

    La chose n'existe pas, elle n'est qu'une possible apparition, mesurée par son existence parmi une multiplicité d'autres, ou bien par un degré de croyance, subjectif mais quantifié, en son apparition.      

    L'attitude

    Mais avant de gloser davantage, il convient de revenir aux fondamentaux. On se positionnera ici comme un statisticien expérimentateur dont on doit de décrire la posture et l'attitude  "typique".

    D'abord il y a une réalité qu'il mesure, et qui se comporte conformément à un modèle statistique. On fait un test, et la question est: est qu'il s'est passé quelque chose qui dévie du modèle statistique ? 

    L'hypothèse de base  est que non, il ne va rien se passer: l'hypothèse dite "nulle" sera  vérifiée: le monde est bien conforme au modèle évoqué et le test lui appartient. A moins que, et là on discute. Dans certaines circonstances, il va falloir décider de rejeter l'hypothèse nulle et d'adopter l'hypothèse alternative. Dans lesquelles ? 

    Le test se traduit par une distribution de probabilité, disons par un pic, qu'on va positionner par rapport au modèle connu. Pour cela, on va calculer une valeur dite "p-value" ou "valeur p" qui va être la probabilité pour que dans le modèle connu, on soit encore éloigné encore plus de la moyenne connue que la mesure. Si l'hypothèse zéro est représentée par une densité de probabilité, on va l'intégrer au delà de la valeur moyenne de la mesure test.

    Cette probabilité va alors être comparée à un seuil, par exemple 5% (ou 1%) et si elle est plus petite, alors là on va commencer à douter de l'hypothèse zéro, voire la rejeter. Une mesure répétée se doit d'être DANS la courbe du monde tel qu'il décrit, ou bien elle n'est pas bonne. Pas bonne ? Au contraire, comme elle est une expérience, c'est elle qui EST bonne, et c'est la description a priori du monde qui doit être rejetée. 

    A noter que l'anormalité de la mesure par rapport à une description qui se trouve fausse se trouve mesurée dans le cadre de la fausseté, qui se trouve alors prouvée comme incohérente avec le réel et donc fausse... 

    On notera qu'il y a dans le monde fréquentiste deux attitudes, celle du vénérable Fisher qui veut prouver rationnellement et celle du moderne Pearson qui veut lui décider. Les deux attitudes sont mixées dans les pratiques scientifiques courantes. Notons que Fisher adopte une attitude Popérienne: l'hypothèse nulle est la référence et reste une hypothèse réfutable. Quand quelque chose de bizarre se  produit, soit il s'agit d'un évènement rare possible et compatible avec la nullité de l'hypothèse, soit celle ci doit être remise en cause. C'est la fameuse "disjonction de Fisher" qui caractérise l'asymétrie Poperienne, critiquée par ailleurs.

    Cette  histoire de p-value doit être comparée avec une autre histoire, qui est celle de l'intervalle de confiance, autre manière théoriquement équivalente de présenter les choses. L'intervalle est centré sur la moyenne du modèle, et a pour largeur 4 ou 6 fois l'écart type. Il faut que le test soit dedans pour que l'on puisse garder l'hypothèse nulle. 

    La Gaussienne

    Dans tous les cas, on se retrouve bien sur avec la répartition des observations possibles sur les abcisses d'une gaussienne... 

    C'est la fameuse courbe en cloche, archétype de la courbe de densité de probabilité, limite continue des diagrammes en bâtons donnant chacun la probabilité d'un bloc de valeurs.  

    Central Limite

    De fait, le "théorème central limite" affirme que la somme de n lois gaussiennes d'écart type sigma donnera une loi gaussienne d'écart type sigma/racine(n). Plus n est grand, plus l'écart type final sera petit.

    Le fameux théorème, merveille de la nature s'applique même si les lois des échantillons ne sont pas gaussiennes: dans ce cas, le résultat sera en cloche, mais avec un écart type non calculé ou à calculer... 

    C'est ce qui justifie les sondages: si on prend un échantillon "assez" grand de taille N (la limite est 30), son écart type sera l'écart type de la "vraie" situation multiplié par racine(N). En fait c'est l'inverse c'est à dire que c'est l'écart type de l'échantillon qui divisé par racine(N) donne le "vrai" écart type... Celui ci est forcément petit: il "affine" les échantillons.

    Mais bon, l'écart type sigma d'un échantillon permet ainsi de calculer de manière "sure" le vrai écart type... A partir de là l'intervalle de confiance de la moyenne de l'échantillon, qui elle est imprécise, sera de plusoumoins 2*sigma/racine(N) à 4 sigma et on peut y aller (programme de seconde).

    On remarque que g(2*sigma, sigma) vaut 0,05 pour sigma valant 1, il suffit de regarder le dessin d'ailleurs. Mais en fait, le coup des 95% est en fait l'intégrale de la gaussienne entre -2*sigma et +2*sigma, qui vaut 0,95... On dira que la moyenne effective est donnée par la moyenne de l'échantillon "avec une confiance de 95%". 

    Revenons au théorème central limite; sa formulation exacte est que la limite de la somme de tout ensemble de lois d'écarts types sigma dans un intervalle donné, divisé par sigma*racine(n) et centrées sur leurs moyennes sera l'intégrale de la gaussienne centrée réduite sur cet intervalle. La division par sigma permet de se ramener à la courbe archétype.

    La Gaussienne centrée réduite

    Rappelons que la fameuse courbe centrée sur zéro si elle a un paramètre sigma de 1, représente une distribution de probabilités dont l'écart type est sigma, a pour valeur maximale 0.4, est quasiment nulle en 3, et a pour valeur en 1 0.25. L'intégrale de la courbe est bien sur UN. 

    gaussienne.png

      

    On notera l'utilisation de sigma, ici UN, le "3 sigma", opportunément transformé en "6 sigma" pour prendre toute la largeur de la courbe, et qui caractérise toutes les possibilités pour un objet d'être DANS l'hypothèse nulle. De fait, être hors du 6 sigma, c'est vraiment mal, ça ne devrait jamais se produire, et l'hypothèse nulle doit être rejetée... 

    Linguistiquement, le 6 sigma est un critère de qualité, il permet, quand on VEUT imposer l'hypothèse nulle, de rejeter la mesure, comme non conforme au critère, quand on veut imposer soit la vérité absolue de l'hypothèse, soit une "politique" de qualité. L'objet fabriqué selon la mesure foireuse peut alors être poubellisé. 

    On va alors considérer le 4 sigma. Et bien il se trouve, et cela reste à démontrer que l'on a là précisément le fameux intervalle de confiance à 95 % dont tout le monde parle !! En gros, l'intervalle de confiance c'est  plusmoins 2 sigma.

    Pour le démontrer, fastoche. La loi gaussienne réduite ci dessus a pour équation

    g(x,sigma) = exp(-x^2/2*sigma^2) / sigma*racine(2*PI)  avec sigma == 1

    Courbe de densité de probabilité, dont l'intégrale dit "gaussienne" vaut 1 entre moins et plus l'infini. 

    ll se trouve que cette intégrale n'a pas de formule simple, et n'est donnée que par des tables ! Par exemple le fameux 0,95 est incalculable à la main et ne vaut en fait non pas pour 2, mais pour 1,96.... La fonction PHI, répartition de la gaussienne de moins l'infini à x n'a pas d'expression analytique ! 

    Un autre point est qu'on peut aussi jouer avec la forme initiale du théorème, qui partait d'une somme de "lois" (ou de "distributions") binomiales (les tirages à pile ou face avec une pièce truquée). Ces sommes de lois sont appellées aussi "de Bernouilli", le tirage principal ayant la probabilité "p", et on fait "N" tirages. La "loi" c'est la probabilité d'obtenir k fois la bonne face, avec k entre 0 et n. La loi c'est:

    C(n, k) p^k (1-p)^(n-k))

    Quand N tend vers l'infini, cette loi, dont l'écard type est racine(p*(1-p)*N), centrée sur sa moyenne N*p, et divisée par son écard type, et tend vers la gaussienne centrée réduite.

    Un peu d'histoire

    Laplace brilla avec cette histoire, en appliquant son raisonnement, tout issu des lumières, à la proportion de naissance de garçons dans la population qui se trouve supérieur à celui des filles, et cela partout en Europe, dans un rapport de 22 à 21. Or, en cinq ans, sur 2009 naissances à Carcelles le Grignon on observa la naissance de 1026 filles. Et bien cela est à l'intérieur de l'intervalle de confiance à 2 sigma ! Laplace exprime la chose en se ramenant au jeu de "croix" et "pile": la probabilité pour que cela arrive est inférieure à celle de tirer 4 fois "croix" de suite et donc non significative.

    Les lois dérivées de la loi normale.

    On a deux lois dérivées de la gaussienne, et qui servent dans les tests d'hypothèses. 

    Student

    D'abord quand le nombre d'éléments d'un échantillon test est inférieur à 30, on n'a pas la loi normale comme aggrégation des échantillons, mais la loi de Student à N degrés de libertés, N étant la taille de l'échantillon - 1, sachant que les probabilités ont pour somme 1. On fait comme avec la loi normale, à part qu'on regarde dans la table de Student pour contrôler la p value. 

    kiki hideux

    Et puis on a le Khi 2, X^2. Là c'est tout une poème car cela ne ressort pas du test d'hypothèses à proprement parler, mais d'un autre types de test, quoique la même méthodologie soit mise en oeuvre. 

    Le test archétype est celui de l'indépendance de deux caractères. On part d'une distribution en n caractères (n plus petit que 10, ce sont les degrés de liberté) et on compare avec une distribution test.

    L'idée est que sous l'hypothèse nulle, et qui est toujours que tout va bien, on va calculer "une statistique du khi 2", un nombre dont on va mesurer dans une table s'il a pour probabilité une valeur inférieure à un seuil d'acceptablité. Si oui, et bien on peut rejeter l'hypothèse nulle... 

    Il faut bien comprendre que le khi deux n'est qu'une variante de la gaussienne, permettant de jauger les probabilités d'apparition -sous l'hypothèse nulle- d'une répartition particulière de valeurs dans des tableaux croisés. 

    On se ramène donc à calculer un tableau croisé de référence, conforme à la distribution de l'hypothèse nulle, et on calcule alors une "statistique", le nombre:

         Sigma (1,n)   (xi_observé - xi_référence)^2 / nb xi 

    Puis, on détermine le nombre de degrés de liberté, typiquement "n - 1". 

    On consulte alors la table du khi deux.... 

    On remarque ainsi que à 5%, la table donne pour les degrés de liberté 1,2,3 les valeurs 4,6,8

     

    Le Sida encore

    Reprenons en considérant la valeur-p pour le test du Sida. L'hypothèse nulle est de ne pas avoir le Sida, bien sur... Le test est positif, ce qui n'arrive que dans 1% des cas. C'est super faible n'est ce pas ? Et bien non ! Cela n'est pas du à la gaussienne, mais à la prévalence et au fait que la valeur-p ici de 1% (probabilité conditionnelle testpositif/passida) est abusivement décisionnelle si comparée à 5%.

    La stratégie de la p valeur est donc prise en défaut, si on ne se méfie pas. Ce qui fait que certains recommandent toujours le seuil de 1% (4). Cela fut remarqué dans les années 2000 à la suite d'un grand nombre de p values trop minuscules (ou estimées telles) responsables de l'apparition trop non reproductible de phénomènes extraordinaires. 

    On tremble en pensant au glyphosate... 

    Le facteur bayésien est considéré supérieur, il s'agit de calculer H0/x : H1/x  , le rapport des vraisemblances. 

    Pour ce qui concerne notre cas, on obtient oui/ok : (1 - oui/ok) = 10^(s-q).

    Plus q est  grand, c'est à dire que la prévalence est faible, et bien on mesure l'insignifiance du test, c'est à dire son incapacité à contrer l'hypothèse nulle. Au contraire, si la sensibilité suffisante, le facteur sera supérieur à UN, et donc le test "fort": on pourra alors avoir le sida... 

    Le facteur de Bayes est un bien meilleur estimateur que la p-value ! 

      

    (1) http://www.laeuferpaar.de/Papers/Sprenger_Bayes+Freq.pdf

    (2) http://www.aly-abbara.com/utilitaires/statistiques/sensibilite_specificite_vpp_vpn.html#Q

    (3) Pearson, Fisher et Bayes: http://udsmed.u-strasbg.fr/labiostat/IMG/pdf/testfreq.pdf

    (4) https://royalsocietypublishing.org/doi/full/10.1098/rsos.140216

    (5) le khi deux https://alea.fr.eu.org/git/doc_khi2.git/blob_plain/HEAD:/khi2.pdf

    (6) la table du khi deux: http://www.math.univ-metz.fr/~bonneau/STAT0607/table_khi2_complete.pdf

  • Les islams

    En ces temps troublés où il n'y a pas de musulman non pris les armes à la main qui ne parle de réforme, tout comme d'ailleurs aussi leurs adversaires inexpiables qui sont aussi les nôtres, c'est dire si le sujet est important; il convient de décrire l'un des points fixes de cette fameuse réforme, tout en re-précisant son ancienneté, c'est dire si le sujet est futile. 

    On se rapportera à (1) et (2) pour plus de détails, le sujet fut déjà fouillé. 

    Je veux parler du Mutazilisme, l'apostrophe après le "mu" étant omis, notre maniérisme étant limité par notre fainéantisme. On en avait parlé en (1). On recommence. 

    Le Mutazilisme c'est d'abord celui qui s'abstient lors de la grande Fitna. Rattaché à Ali (Wikipédia s'obstine toujours à parler de lui sous la forme "Ali Ibn Abi Taleb" est bien sur le gendre du prophète, l'initiateur de la fitna, l'inspirateur du chiisme. 

    Pour résumer l'histoire de la Fitna (discorde), il faut savoir qu'Ali le cousin et gendre (Mahomet n'eut que des filles, dont Fatima), aurait du être le successeur. Las ! Ce fut Abu Bakr, auquel succéda Omar tué par un perse, puis Othman, qui épousa deux filles de Mahomet (Rukkaya et Oum Kultum) lui même assassiné. Et ce fut le tour d'Ali, lui même accusé d'avoir fait tuer Othman. 

    Il est désigné dépositaire par Mahomet à Ghadir Khumm, à mi chemin entre la mecque et médine, de la "mawla" interprétée différemment par les sunnites, chiites et soufis, respectivement comme l'autorité familiale, religieuse et spirituelle. 

    Mahomet donna son sabre (Zulfikar) à Ali, qui s'en servit magnifiquement (le classique et très moyen âgeux découpage depuis le sommet du crane jusqu'à la moitié de la poitrine d'un méchant (sans doute)). D'où le très "religion de paix": "il n'y a pas de sabre comme Zulfikar, il n'y a pas de héros comme Ali". 

    Ce fut la "bataille du chameau", avec Aicha la femme de Mahomet (mariée à 6 ans, elle ne fut consommée qu'à 10, mais bien sur il a d'autres âges respectifs mentionnés), sur un chameau elle dirige l'armée et perd la bataille quand ses troupes se débandent, après qu'on ait coupé les jarrets du chameau... 

    La bataille en question fut décisive et se trouve être la manière dont on philosophe en islam au sujet du un et du multiple. C'est le fameux "il y a 73 sectes en islam, toutes iront en Enfer sauf un(e)"... 

    Mais revenons en arrière, à l'issue de la bataille de Siffrin, Ali traite avec Muhawiya, ce qui est refusé par les kharidjites qui le tuent. Muhawiya fonde le califat Omeyyade, capitale Damas. 

    Les kharidjites, dissidents de l'islam par excellence, se divisent en blancs (ibadites) jaunes (sufrites) et bleus (azraquites). Les ibadites sont encore à Oman, à Djerba en Tunisie et au Mzab en Algérie.  

    Puis vinrent ceux qui trucidèrent les Ommeyyades,les Abassides, capitale Bagdad. 

    C'est d'ailleurs le rejet du pluralisme par le très autoritaire calife abbasside mutaziliste Al Mamun qui causa le rejet de la belle doctrine, et d'ailleurs de toute l'autorité religieuse du calife. A vouloir imposer par la force un coran crée, on créa par réaction la belle religion de paix sous sa forme actuelle et cela au nom de la très civilisée séparation des pouvoirs...

    Après Al Achari, l'ex mutazilite qui fonda l'acharisme, le mutazilisme disparut complètement n'exista plus que comme le repoussoir philosophique et religieux. Bien sur il s'allia au maghreb avec les kharidjites ibadites mais c'est pour la forme: un repoussoir vous dis-je.  

    Mutazilisme 

    Le Mutazilisme fut en quelque sorte le contraire du Khardjitisme: il fut l'école théologique qui ne prenait pas parti et qui se donnait des critères rationnels, il se trouve être l'objectif des réformateurs avec évidemment le tort de contredire les doxas installées: coran créé et actes crées par l'homme. Ca fait beaucoup. 

    Fondé par Wasil ibn Ata à Basra, il marque la ville, impliquée dans le drame de la mort d'Othman. 

    Quand on pense que El Gazhali, le maitre des soufis dit le contraire exact et s'oppose à tout rationalisme, on mesure l'abime qui sépare les réformateurs des autres. Cela fut décrit en (3).  

    On associe le Mutazilisme rationalisateur avec l'école juridique dite Hanafite (la turque, celle qui avait cour dans l'empire Ottoman) la plus libérale, celle qui ose le recours à l'analogie pour interpréter. C'est aussi la seule qui ne récuse pas le talion entre un Dimmi et un musulman dans le sens de gauche à droite... Par contre le frère Qaradawi serait hanafite, comme  quoi. 

    Sur la question des "actes" des hommes, le mutazilisme nie donc que Dieu soit le créateur des désobéissances, et de la mécréance. L'homme est ainsi libre et se trouve récompensé et puni. Dieu ne crée pas et ne veut pas le mal. Cette asymétrie entre bien et mal, le mal  n'est qu'une non existence, permet ainsi de rendre l'homme seul capable du mal, de part sa liberté. C'est le fameux 4.79 "tout le bien vient de Dieu, tout le mal de l'homme". Pourtant il y a aussi: 37.96 "il vous a crée et tout ce que vous faites"... 

    Initiée par la première des grandes voies islamiques la Quadariyya, cette manière de voir conduit à un juridique examinable par la raison, le bien et le mal étant accessible à l'homme. Permettant un statut du pécheur entre apostasie et soumission totale, la "demeure entre les demeures" est typique de l'approche Mutaziliste.

    Par ailleurs l'affirmation d'un coran crée introduit l'accusation qui leur est faite d'"associer" Dieu à quelque chose d'autre. Hérésie et malédiction. 

    Pour compléter les choses, les Mutazilites associent intelligence et volonté alors que la réaction acharite donne tout pouvoir à la volonté (de Dieu). 

    Al Gazhali dans "le juste milieu dans la croyance" fait discuter le mutaziliste Al Gubai et Al Achari lui même sur la question des 3 frères, le pieux, le mécréant et le mort en bas âge. Qu'arrive-t-il au mort né ? Alors que le  mutaziliste dit que Dieu ne le punit ni ne le récompense car il voulait le faire mécréant s'il l'avait laissé vivre, Al Achari rétorque que le mécréant aurait du donc mourir en bas âge...  

    Achari apporte toutefois une très légère dose de liberté dans l'attribution à Dieu de toute la volonté. Assez pour disculper d'un total déterminisme, assez peu pour ne pas devoir s'y soumettre (5). Disons que l'homme "acquiert" un acte crée par Dieu, le "kasb" étant l'acte volontaire complexe, à discuter et de multiples interprétations furent proposées... 

    Il faut ajouter un camp dans cette belle discussion: celui des falsifa, euh des "philosophes", qui se différenciaient nettement des mutazilites mais qui furent tout autant rejetés et anathémisés. Farabi, Avicenne, Rushd voient le monde comme nécessaire. Encore pire: Dieu est un moteur raisonnable, infiniment puissant et à qui il faut s'unir. Tout ceci, qui se voulait décrire rationnellement l'infiniment arbitraire, fut rejeté avec violence. 

    Dans la réalité, le refus il y a mille ans, non seulement de la philosophie, mais de la seule alternative théologique à l'écrasement absolu de l'humain devant une divinité autoritaire dont l'unicité ne cédait rien à la cruauté et au total manque d'intérêt pour le développement humain, consacre l'impossibilité complète de faire quoi que ce soit avec la religion en question. Elle doit disparaitre, la voilà la réforme qu'on cherche. 

     

    (1) http://francoiscarmignola.hautetfort.com/archive/2016/09/17/les-theologies-musulmanes-5848990.html 

    (2) http://francoiscarmignola.hautetfort.com/archive/2015/11/11/les-ecoles-musulmanes-5714316.html

    (3) http://francoiscarmignola.hautetfort.com/archive/2016/09/11/le-dernier-philosophe-5846460.html

    (4) Doctrine Mutazilite: https://www.persee.fr/doc/remmm_0035-1474_1973_num_13_1_1218

    (5) la doctrine acharite:  http://openaccess.uoc.edu/webapps/o2/bitstream/10609/54442/5/La%20pens%C3%A9e%20classique%20arabe_Module4_Le%20kal%C3%A2m%20d%27Al-Ash%27Ari.pdf