9
votes

Fonctions de distribution de probabilité (PDF) à partir d'histogrammes

Dis que j'ai plusieurs histogrammes, chacun avec des comptes à différents emplacements de bacs (sur un axe réel). par exemple xxx

Comment puis-je normaliser ces histogrammes afin que je reçois pdfs où l'intégration de chaque pdf ajoute à un dans une donnée gamme (par exemple 0 et 100)?

Nous pouvons supposer que l'histogramme compte les événements sur taille de bac prédéfinie (par exemple 10)

la plupart des implémentations que j'ai vues sont basées, par exemple, par exemple, sur noyaux gaussiens (voir < Code> SCIPY et Scikit-apprendre < / code> ) qui commencent par les données. Dans mon cas, je dois le faire à partir des histogrammes, car je n'ai pas accès aux données d'origine.

Mise à jour:

Notez que toutes les réponses actuelles supposent que nous sommes En regardant une variable aléatoire qui vit (-inf, + inf). C'est bien comme une approximation approximative, mais cela peut ne pas être le cas en fonction de l'application, où la variable peut être définie dans une autre plage [A, B] (par exemple, 0 et 100 dans le cas ci-dessus. )


20 commentaires

Merci @aaronhall j'ai corrigé l'OP.


Je pense que c'est une très bonne question, je voterais, mais je suis à court de voix pour la journée. Je suis d'accord, je ne pense pas que vous devriez commencer avec l'hypothèse de la normalité.


Un KDE (gaussien ou autre) fonctionne de manière intrinsèquement sur les données, de sorte que cela ne vous aidera pas dans votre cas.


À quoi sert le PDF? Y a-t-il des contraintes à ce sujet (telles que les limites inférieures ou supérieures connues sur les données)? Savez-vous autre chose à propos de la distribution de ces données? Dans tous les cas, je pense que vous devriez penser dans le sens des distributions de mélange. Lequel que vous utilisez spécifiquement dépendrait des réponses à ces questions.


Merci @jcrudy, j'ai probablement un mélange de gaussiens, mais je ne connais pas le nombre de mélanges. Est ce que ça aide?


Vous attendez-vous à ce que bin_locations décrit les centres des bacs? Sinon, (s'ils sont des bords des bacs), vous devriez avoir len (bin_counts) = len (bin_locations) - 1


@ASKEWCHAN OUI, bin_locations sont les emplacements centraux


Il y avait un fil sur ce sujet sur la liste de diffusion SCIKIT apprendre il y a un moment. Voici un lien: Sourceforge.net/p/scikit-learn/mailman/ Message / 31984328


Lorsque vous dites "avoir la même gamme de x" voulez-vous dire que tous les bin_locations doivent être identiques ou pourrez-vous dire simplement plage comme dans la plage Mot-clé Arg to np.histogramme qui indique juste les limites inférieure et supérieure du domaine?


@ASKEWCHAN: Ce que je veux dire, c'est que nous pouvons supposer que toute la probabilité est contenue dans une plage spécifiée ( 0 à 100 dans l'OP) pour chaque histogramme.


Vous l'avez édité légèrement, mais pour moi "PDF" n'est toujours pas bien défini dans votre question. Voulez-vous une vraie fonction définie sur l'ensemble du domaine (comme ce que gaussian_kde ou une interpolation reviendrait)? Ou est-ce correct d'avoir un fichier PDF semblable à une matrice avec des valeurs à bin_locations , tant qu'il est correctement normalisé sur le domaine? Ou peut-être que vous voulez quelque chose entre entre-temps, dites-vous des bacs uniformément espacés standardisés que les valeurs de tous les histogrammes sont données à (comme np.linspace (0.100) )?


@ASKEWCHAN Je veux un vrai PDF défini sur une plage pré-spécifiée, dans ce cas [0.100] . Pour que ce soit un PDF, l'intégrale du PDF dans la plage devrait être 1. Puisque nous travaillons sur le domaine réel, je devrais être capable de le discrétiser à des intervalles réguliers (par exemple, en utilisant np.linspace ).


Si le PDF s'intègre à 1 sur [0, 100], il n'est pas un mélange de Gaussiens. Voulez-vous dire que cela signifie qu'un soutien de [0, 100] est une contrainte difficile, ou n'essayez-vous que d'illustrer que cela doit être un véritable PDF pour une distribution continue avec support inclut [0,100]?


@jcrudy - Vous êtes correct, ils ne seraient pas un mélange de gaussiens, mais si vous avez la contrainte que la densité est contrainte dans [A, B] est trop difficile, nous pouvons détendre la contrainte pour (- INF, INF)


Ensuite, vous devez interpoler. Il existe de nombreuses options et sans plus d'informations supplémentaires, il n'y a aucun moyen de maximiser l'entropie. Le KDE I a affiché plus tôt s'intègre à 1 sur (-Inf, Inf) et le réseau normalisé s'intègre à 1 sur [0,100], mais je supposais que vous avez trouvé ceux-ci insatisfaisants.


"Nous pouvons supposer que l'histogramme compte les événements sur la taille de la corbeille prédéfinie (par exemple 10)" Nous ne pouvons pas si vous donnez des centres de bacs aléatoires. Comment ces BIN_LOCATIONS ont-ils une binsisation uniforme: [. 1 , 5, 50, 90] ?


Je viens de poster une possibilité GMM. C'est un peu faible car cela implique de se réaménager à partir de l'histogramme, puis de fixer une GMM aux données rénovées. Avez-vous envisagé d'utiliser PYMC? Avec PYMC, il pourrait être possible de faire quelque chose d'un peu plus efficace (statistiquement parlant). Cependant, l'inférence bayésienne peut être un trou de lapin.


@ASKEWCHAN Nous pouvons faire le bin_size beaucoup plus petit si cela provoque un problème. Techniquement parlant, pour rendre le problème bien défini, les numéros d'histogramme doivent correspondent à correspondant à intervalles (sinon, il n'a pas de sens de parler de PDFS). Ces données sont fausses après tout, mais chaque compte doit correspondre à un intervalle bien défini.


Merci @jcrudy - Je le regarde maintenant. Oui - T a étiqueté la question avec PYMC , puisque je pensais pymc pourrait aider avec elle.


Oh, je n'avais pas remarqué la balise. Je verrai si je peux préparer un exemple de PYMC rapide.


3 Réponses :


6
votes

Le point principal de la délicatesse est défini sur bin_edges car, techniquement, ils pourraient être n'importe où. J'ai choisi le milieu entre chaque paire de centres de bacs. Il y a probablement d'autres moyens de faire cela, mais voici un: xxx

ce qui entraînerait des parcelles comme les suivantes (une pour chaque histogramme): histes


11 commentaires

Je suis confus. Qu'est-ce que c'est censé calculer? Quand j'exécute cela, je reçois un tableau complet de 0s pour bin_counts .


Désolé, faites votre bin_counts avoir dtype de flotteur en premier. Je vais éditerai.


Merci - quel code exactement de votre réponse vous avez utilisé pour générer la parcelle inférieure? (qui ressemble à peu près à droite). Était-ce celui utilisant des données "fausses"?


Tout cela, courir ensemble. Assurez-vous d'exécuter la partie «KDE» avant de normaliser le tableau bin_counts (car je normalisé en place) et les poids de KDE utilisant np.repeat et nécessitent les comptes pour être entiers . Notez que la KDE a une probabilité finie en dehors du domaine.


Merci - bien que ce ne soit toujours pas clair pour moi. Souhaitez-vous peut-être réorganiser la réponse à montrer quelle extrait de code vous exécutez de bout en bout pour obtenir la parcelle?


Désolé. Edité --- Exécutez maintenant tout le code formaté, dans l'ordre et que vous devriez obtenir la parcelle. (Après avoir créé la liste Liste des histogrammes aléatoires, bien sûr). Cela vient de parcourir la dernière entrée dans Histeurs . Mettez le PLT Fonctions à l'intérieur de la boucle si vous souhaitez tracer chacun.


Hmm je n'obtiens pas le même résultat. Je pense que vous normalisez le bin_counts avant plt.step de sorte que la plage est à peu près équivalente. Comment avez-vous normalisé les comptes?


Oh désolé, changé de nom avariable. plt.step avec bin_density maintenant


J'ai mis à jour votre réponse pour refléter ce changement. J'espère que ça ne vous dérange pas


Merci de l'acceptation. Je pense avoir la contrainte totale sur sur [A, B] , vous devrez faire un ajustement des moindres carrés ou quelque chose avec la contrainte que l'intégration est égale à une. La question est alors quelle fonctionnelle pour affronteriez-vous (ici, c'est une constante par étapes ou une somme de Gaussiens).


Oui - je viens de gérer aussi SeaNorn . J'ai utilisé par exemple leur violonplots , qui semblait estimer un PDF à partir de données d'histogramme. Je ne sais pas ce qui est sous le capot.



3
votes

Voici une possibilité. Je ne suis pas si fou de ça, mais cela fonctionne en quelque sorte. Notez que les histogrammes sont un peu étrange, car la largeur de la corbeille est assez variable.

import numpy as np
from matplotlib import pyplot
from sklearn.mixture.gmm import GMM
from sklearn.grid_search import GridSearchCV

def generate_random_histogram():
    # Random bin locations between 0 and 100
    bin_locations = np.random.rand(10,) * 100
    bin_locations.sort()

    # Random counts on those locations
    bin_counts = np.random.randint(50, size=len(bin_locations))
    return {'loc': bin_locations, 'count':bin_counts}

def bin_widths(loc):
    widths = []
    for i in range(len(loc)-1):
        widths.append(loc[i+1] - loc[i])
    widths.append(widths[-1])
    widths = np.array(widths)
    return widths

def sample_from_hist(loc, count, size):
    n = len(loc)
    tot = np.sum(count)
    widths = bin_widths(loc)
    lowers = loc - widths
    uppers = loc + widths
    probs = count / float(tot)
    bins = np.argmax(np.random.multinomial(n=1, pvals=probs, size=(size,)),1)
    return np.random.uniform(lowers[bins], uppers[bins])

# Generate the histogram
hist = generate_random_histogram()

# Sample from the histogram
sample = sample_from_hist(hist['loc'],hist['count'],np.sum(hist['count']))

# Fit a GMM
param_grid = [{'n_components':[1,2,3,4,5]}]
def scorer(est, X, y=None):
    return np.sum(est.score(X))
grid_search = GridSearchCV(GMM(), param_grid, scoring=scorer).fit(np.reshape(sample,(len(sample),1)))
gmm = grid_search.best_estimator_

# Sample from the GMM
gmm_sample = gmm.sample(np.sum(hist['count']))

# Plot the resulting histograms and mixture distribution
fig = pyplot.figure()
ax1 = fig.add_subplot(311)
widths = bin_widths(hist['loc'])
ax1.bar(hist['loc'], hist['count'], width=widths)
ax2 = fig.add_subplot(312, sharex=ax1)
ax2.hist(gmm_sample, bins=hist['loc'])
x = np.arange(min(sample), max(sample), .1)
y = np.exp(gmm.score(x))
ax3 = fig.add_subplot(313, sharex=ax1)
ax3.plot(x, y)
pyplot.show()


1 commentaires

C'est une excellente réponse. Merci@jcrudy. Je Certainement serait intéressé à voir une solution avec PYMC.



2
votes

Voici un moyen de le faire avec PYMC. Cette méthode utilise un nombre fixe de composants (N_Components) dans le modèle de mélange. Vous pouvez essayer de joindre un antérieur à N_Components et d'échantillonner à ce sujet avant. Sinon, vous pouvez simplement choisir quelque chose de raisonnable ou utiliser la technique de recherche de la grille de mon autre réponse pour estimer le nombre de composants. Dans le code ci-dessous, j'ai utilisé 10000 itérations avec une gravure de 9 000, mais cela pourrait ne pas être suffisant pour obtenir de bons résultats. Je suggérerais d'utiliser une brûlure beaucoup plus importante. J'ai aussi choisi les prores un peu arbitrairement, de sorte que celles-ci pourraient être quelque chose à regarder. Vous devrez expérimenter avec elle. Bonne chance à vous. Voici le code.

import numpy as np
import pymc as mc
import scipy.stats as stats
from matplotlib import pyplot

def generate_random_histogram():
    # Random bin locations between 0 and 100
    bin_locations = np.random.rand(10,) * 100
    bin_locations.sort()

    # Random counts on those locations
    bin_counts = np.random.randint(50, size=len(bin_locations))
    return {'loc': bin_locations, 'count':bin_counts}


def bin_widths(loc):
    widths = []
    for i in range(len(loc)-1):
        widths.append(loc[i+1] - loc[i])
    widths.append(widths[-1])
    widths = np.array(widths)
    return widths


def mixer(name, weights, value=None):
    n = value.shape[0]
    def logp(value, weights):
        vals = np.zeros(shape=(n, weights.shape[1]), dtype=int)
        vals[:, value.astype(int)] = 1
        return mc.multinomial_like(x = vals, n=1, p=weights)

    def random(weights):
        return np.argmax(np.random.multinomial(n=1, pvals=weights[0,:], size=n), 1)

    result = mc.Stochastic(logp = logp,
                             doc = 'A kernel smoothing density node.',
                             name = name,
                             parents = {'weights': weights},
                             random = random,
                             trace = True,
                             value = value,
                             dtype = int,
                             observed = False,
                             cache_depth = 2,
                             plot = False,
                             verbose = 0)
    return result

def create_model(lowers, uppers, count, n_components):
    n = np.sum(count)
    lower = min(lowers)
    upper = min(uppers)
    locations = mc.Uniform(name='locations', lower=lower, upper=upper, value=np.random.uniform(lower, upper, size=n_components), observed=False)
    precisions = mc.Gamma(name='precisions', alpha=1, beta=1, value=.001*np.ones(n_components), observed=False)
    weights = mc.CompletedDirichlet('weights', mc.Dirichlet(name='weights_ind', theta=np.ones(n_components)))

    choice = mixer('choice', weights, value=np.ones(n).astype(int))

    @mc.observed
    def histogram_data(value=count, locations=locations, precisions=precisions, weights=weights):
        if hasattr(weights, 'value'):
            weights = weights.value

        lower_cdfs = sum([weights[0,i]*stats.norm.cdf(lowers, loc=locations[i], scale=np.sqrt(1.0/precisions[i])) for i in range(len(weights))])
        upper_cdfs = sum([weights[0,i]*stats.norm.cdf(uppers, loc=locations[i], scale=np.sqrt(1.0/precisions[i])) for i in range(len(weights))])

        bin_probs = upper_cdfs - lower_cdfs
        bin_probs = np.array(list(upper_cdfs - lower_cdfs) + [1.0 - np.sum(bin_probs)])
        n = np.sum(count)
        return mc.multinomial_like(x=np.array(list(count) + [0]), n=n, p=bin_probs)

    @mc.deterministic
    def location(locations=locations, choice=choice):
        return locations[choice.astype(int)]

    @mc.deterministic
    def dispersion(precisions=precisions, choice=choice):
        return precisions[choice.astype(int)]

    data_generator = mc.Normal('data', mu=location, tau=dispersion)

    return locals()

# Generate the histogram
hist = generate_random_histogram()
loc = hist['loc']
count = hist['count']
widths = bin_widths(hist['loc'])
lowers = loc - widths
uppers = loc + widths

# Create the model
model = create_model(lowers, uppers, count, 5)

# Initialize to the MAP estimate
model = mc.MAP(model)
model.fit(method ='fmin')

# Now sample with MCMC
model = mc.MCMC(model)
model.sample(iter=10000, burn=9000, thin=300)

# Plot the mu and tau traces
mc.Matplot.plot(model.trace('locations'))
pyplot.show()

# Get the samples from the fitted pdf
sample = np.ravel(model.trace('data')[:])

# Plot the original histogram, sampled histogram, and pdf
lower = min(lowers)
upper = min(uppers)
kde = stats.gaussian_kde(sample)
x = np.arange(0,100,.1)
y = kde(x)
fig = pyplot.figure()
ax1 = fig.add_subplot(311)
pyplot.xlim(lower,upper)
ax1.bar(loc, count, width=widths)
ax2 = fig.add_subplot(312, sharex=ax1)
ax2.hist(sample, bins=loc)
ax3 = fig.add_subplot(313, sharex=ax1)
ax3.plot(x, y)
pyplot.show()


4 commentaires

C'est une réponse très intéressante. Quelque chose que je ne comprends pas, d'un point de vue statistique, pourquoi la réponse de l'Askewchan peut s'en tirer sans devoir estimer le nombre de mélanges. Utilise-t-il une méthode bayésienne non paramétrique? Comment votre réponse a-t-elle relativement à son?


Il semble certainement utiliser une méthode non paramétrique. Cela m'a fait me demander, PYMC incluait-il les intégrés à modéliser des distributions non paramétriques?


Je n'ai pas regardé la réponse de @ Askewchan en détail, mais si je comprends bien, il définit implicitement un GMM avec un composant pour chaque barre de l'histogramme, où le poids de ce composant est le nombre de points de données contribuant à cette barre et La variance est déterminée par la bande passante de la KDE. Dans cette réponse, je choisis simplement le nombre de composants dans le mélange arbitrairement. Dans mon autre réponse, j'utilise une recherche de grille pour trouver le nombre de composants. Je pense que PYMC a maintenant un nœud stochastique basé sur la densité de noyau disponible. Voir Stackoverflow.com/a/17934913/1572508 .


D'un point de vue statistique, je ne suis pas sûr de la manière dont aucune de ces trois méthodes peut être justifiée. La question à poser est la suivante: quelle taille peut-on dire que le PDF estimé est au PDF des données qui ont généré l'histogramme d'origine? Avec ces histogrammes, je pense qu'il n'y a pas beaucoup d'espoir d'une reconstruction fiable, simplement parce que les bacs sont larges et qu'il n'y a pas de points de données. Cependant, ils ne sont que des exemples. Un histogramme avec des bacs plus étroits et plus de données feraient mieux. La façon de savoir serait de faire des simulations avec des PDF connus.