6  Kalibrierung

Die Genauigkeit von Messungen wird durch die Korrektion systemtischer Messabweichungen verbessert. In diesem Kapitel werden typische Fehlerarten bei Messungen behandelt:

In diesem Abschnitt greifen wir die etablierten Begriffe auf, auch wenn die Fehler besser als systematische Messabweichungen bezeichnet werden sollten.

6.1 Kalibrieren und Justieren

Die Korrektion systemtischer Messabweichungen kann auf zwei Arten geschehen: durch Kalibrierung oder Justierung des Messgeräts.

Mit dem Begriff Kalibrierung wird im engeren Sinn die Ermittlung des Zusammenhangs zwischen Messwerten bzw. ihres arithmetischen Mittelwerts und dem vereinbarten richtigen Wert der Messgröße bezeichnet.

Definition 6.1: Kalibrierung nach DIN 1319

“Ermitteln des Zusammenhangs zwischen Meßwert […] oder Erwartungswert […] der Ausgangsgröße […] und dem zugehörigen wahren […] oder richtigen Wert […] der als Eingangsgröße vorliegenden Meßgröße für eine betrachtete Meßeinrichtung […].” (Deutsches Institut für Normung 1995, 22)

Im weiteren Sinn ist mit Kalibrierung auch “die Erstellung einer Korrektionstabelle […], die Ermittlung von Kalibrierfaktoren oder einer (empirischen) Kalibrierfunktion” (Deutsches Institut für Normung 1995, 22) gemeint. Dabei handelt es sich um Methoden, um mit systematischen Messabweichungen behaftete Messwerte zu korrigieren, ohne das Messgerät zu verändern.

Im Unterschied dazu verändert die Justierung das Messgerät dauerhaft.

Definition 6.2: Justierung nach DIN 1319

“Einstellen oder Abgleichen eines Meßgeräts […], um systematische Meßabweichungen […] soweit zu beseitigen, wie es für die vorgesehene Anwendung erforderlich ist.” (Deutsches Institut für Normung 1995, 22)

6.2 Kalibriermethoden nach DIN 1319

In der DIN 1319 werden drei Kalibriermethoden genannt, die hier nur kurz behandelt werden:

  1. Korrektionstabelle,
  2. Ermittlung von Kalibrierfaktoren und
  3. Ermittlung einer (empirischen) Kalibrierfunktion.

Welche Methode verwendet wird, hängt von dem Anwendungsbereich, der Datenverfügbarkeit sowie von der gewünschten Genauigkeit ab.

Methode Anwendung bei … Vorteile Nachteile
Korrektionstabelle nichtlinear, schwer modellierbar einfach zu nutzen viele Daten erforderlich, sonst Interpolation ungenau
Kalibrierfaktoren lineare Abweichung leicht umsetzbar unzureichend bei Nichtlinearität
Kalibrierfunktion nichtlinear, modellierbar sehr genau höherer Aufwand

Korrektionstabelle

Mit der Korrektionstabelle können beliebige Korrektionen dargestellt werden, sofern ausreichend Datenpunkte verfügbar sind. In Kapitel 6.6.1 werden wir ein Beispiel für eine Korrektionstabelle kennenlernen.

Ermittlung von Kalibrierfaktoren

Ein Kalibrierfaktor kann angegeben werden, wenn eine konstante relative Abweichung vorliegt. Ein Beispiel aus der Praxis ist hier auf Seite 1 zu finden.

Ermittlung einer (empirischen) Kalibrierfunktion

Eine Kalibrierfunktion erlaubt die Modellierung komplexer Zusammenhänge. Die Methoden dafür werden im Methodenbaustein Datenfitting und Datenoptimierung vorgestellt.

6.3 Additive Fehler: Der Nullpunktfehler

Der Nullpunktfehler beschreibt die Abweichung der Messgröße von Null, wenn der gesuchte Wert tatsächlich Null ist. Der Nullpunktfehler wird auch als Nullabgleich, Nullpunktverschiebung oder Offset-Fehler bezeichnet. Der Nullpunktfehler ist konstant und wirkt additiv auf jeden Messwert. (ics Schneider Messtechnik)

\[ \text{Messwerte} = \text{wahre Werte} + \text{Nullpunktfehler} \]

# Daten erzeugen
messgröße = np.arange(0, 11)
messwerte = messgröße + 4

# plotten
plt.plot(messgröße, marker = 'o', label = 'wahrer Wert')
plt.plot(messwerte, marker = '^', label = 'gemessener Wert')
plt.plot([0, 0], [messgröße[0], messwerte[0]], linestyle = 'dashed', label = 'Nullpunktfehler', color = 'red')
plt.plot([4, 4], [messgröße[4], messwerte[4]], linestyle = 'dashed', color = 'red')
plt.plot([8, 8], [messgröße[8], messwerte[8]], linestyle = 'dashed', color = 'red')

# Achsenbeschriftung
plt.xlabel('Index') 
plt.ylabel('Merkmalsausprägung')

plt.legend()
plt.show()

Schematische Darstellung des Nullpunktfehlers. Zwei parallele Geraden der wahren und der gemessenen Werte ist am Punkt x = 0 durch eine gestrichelte vertikale Linie verbunden, die den Unterschied zwischen dem wahren Wert y = 2 und dem gemessenen Wert y = 6 verdeutlicht. Die gestrichelte Linie erneut bei x = 4 und x = 8 eingezeichnet.

 

Nullpunktfehler quantifizieren

Der Nullpunktfehler kann leicht bestimmt werden, wenn Messwerte für den Nullpunkt vorliegen und der wahre Wert bekannt ist. (Wenn außerdem bekannt ist, dass keine weiteren systematischen Messabweichungen vorliegen, kann der Nullpunktfehler über einen beliebigen wahren Referenzwert bestimmt werden.) Für die zur grafischen Darstellung angelegten Daten ist dies der Fall.

print(f"Der Nullpunktfehler beträgt: {messwerte[0] - messgröße[0]}")
Der Nullpunktfehler beträgt: 4

Wenn keine Daten für den Nullpunkt vorliegen, kann der Nullpunktfehler mit linearer Regression aus mindestens zwei bekannten Referenzpunkten geschätzt werden. Dazu wird wird eine lineare Regression mit den bekannten Referenzwerten als unabhängige Größe \(x\) und den Messwerten als abhängige Größe \(y\) durchgeführt. Der y-Achsenabschnitt der Regressionsgeraden ist der Nullpunktfehler.

# bekannte Referenzpunkte 
x1 = 4
x2 = 8
y1 = messgröße[x1]
y2 = messgröße[x2]

# Nullpunktfehler bestimmen
lm_referenzpunkte = poly.polyfit(x = [messgröße[x1], messgröße[x2]], y = [messwerte[x1], messwerte[x2]], deg = 1)
print(f"Der Nullpunktfehler beträgt: {round(lm_referenzpunkte[0], 2)}")
Der Nullpunktfehler beträgt: 4.0
Hinweis 6.1: Regression

Eine Schätzung der Regressionsgeraden aus 2 Punkten ist im Allgemeinen weniger zuverlässig als eine Schätzung aus mehreren Messpunkten. Die Beispieldaten sind perfekt linear.

Grafisch wird das Vorgehen deutlich.

# lineare Funktionen berechnen
lm_kennlinie = poly.polyfit(x = [x1, x2], y = [y1, y2], deg = 1)
geschätzte_ideale_kennlinie = poly.polyval(x = np.arange(messwerte.size), c = lm_kennlinie)

lm_messwerte = poly.polyfit(x = [x1, x2], y = [messwerte[x1], messwerte[x2]], deg = 1)
lineare_funktion_messwerte = poly.polyval(x = np.arange(messwerte.size), c = lm_messwerte)

# plotten
## Punkte
plt.plot([x1, x2], [y1, y2], marker = 'o', linestyle = 'none', color = 'C0', label = 'bekannte Referenzwerte')
plt.plot([x1, x2], [messwerte[x1], messwerte[x2]], marker = '^', linestyle = 'none', color = 'C1', label = 'Messwerte an Referenzpunkten')

## Linien
plt.plot(np.arange(messwerte.size), geschätzte_ideale_kennlinie, marker = 'none', linestyle = 'dashed', color = 'C0', label = 'ideale Kennlinie')
plt.plot(np.arange(messwerte.size), lineare_funktion_messwerte , marker = 'none', linestyle = 'dashed', color = 'C1', label = 'lineare Funktion Messwerte')

plt.plot([0, 0], [messgröße[0], messwerte[0]], linestyle = 'dashed', label = 'Nullpunktfehler', color = 'red')
plt.plot([4, 4], [messgröße[4], messwerte[4]], linestyle = 'dashed', color = 'red')
plt.plot([8, 8], [messgröße[8], messwerte[8]], linestyle = 'dashed', color = 'red')

plt.legend()
plt.show()

Eine gestrichelte Linie verläuft ausgehend vom Schnittpunkt der Achsen durch zwei Referenzpunkte. Diese gestrichelte Linie enstpricht der idealen Kennlinie. Eine parallele Linie geht durch die gemessenen Werte. Der Abstand zwischen beiden Linien ist über den Wertebereich konstant und entspricht dem Nullpunktfehler.

 

Nullpunktfehler korrigieren

Der Nullpunktfehler wird durch Subtraktion von den Messwerten korrigiert.

\[ \text{wahre Werte} = \text{Messwerte} - \text{Nullpunktfehler} \]

# Daten erzeugen
messgröße = np.arange(0, 11) + 5
messwerte = messgröße + 2.5

# bekannte Referenzpunkte 
x1 = 2
x2 = 5
y1 = messgröße[x1]
y2 = messgröße[x2]

# Nullpunktfehler bestimmen
lm_referenzpunkte = poly.polyfit(x = [messgröße[x1], messgröße[x2]], y = [messwerte[x1], messwerte[x2]], deg = 1)
nullpunktfehler = lm_referenzpunkte[0]
print(f"Der Nullpunktfehler beträgt: {nullpunktfehler.round(2)}")

# Nullpunktfehler korrigieren
korrigierte_messwerte = messwerte - nullpunktfehler
print(f"Wahre Werte:\n{messgröße}")
print(f"Korrigierte Messwerte:\n{korrigierte_messwerte}")
Der Nullpunktfehler beträgt: 2.5
Wahre Werte:
[ 5  6  7  8  9 10 11 12 13 14 15]
Korrigierte Messwerte:
[ 5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]

Übung Nullpunktfehler

Die Quantifizierung des Nullpunktfehlers ist manchmal schwierig, beispielsweise weil der wahre Nullpunkt einer Größe nur schwer vermessen werden kann oder die Messdaten nicht durch eine einfache Funktion approximiert werden können. In diesem Fall muss eine möglichst gute Schätzung gefunden werden.

In einem Klimaschrank wurden 16 Thermoelemente Temperaturen von -10 °C bis 140 °C in Schritten von 10 °C ausgesetzt. Die Messgenauigkeit des verwendeten Datenloggers TCTempX16 liegt bei 0,01 °C, die Auflösung bei 0,1 °C. Für die Thermoelemente soll der Nullpunktfehler bestimmt werden.

Hinweis 6.2: Nullpunkt

0 ° C auf der Celsius-Skala ist kein sinnvoller Referenzpunkt zur Bestimmung des Nullpunktfehlers, weil die Abwesenheit von Wärme erst bei -273,15 °C gegeben ist. Da der Nullpunktfehler über alle Messwerte konstant ist, kann der Nullpunktfehler geschätzt werden, indem für jede Temperaturstufe der Nullpunktfehler separat geschätzt und die Ergebnisse anschließend gemittelt werden.

Die Messdaten liegen in der Datei ‘01-daten/kalibration_tc.xlsx’. Die Daten können mit dem folgenden Befehl eingelesen werden.

klimaschrank = pd.read_excel(io = '01-daten/kalibration_tc.xlsx', sheet_name = 'T15949 MultiChannel - Daten', skiprows = 6)
print(klimaschrank.info())
<class 'pandas.DataFrame'>
RangeIndex: 2771 entries, 0 to 2770
Data columns (total 18 columns):
 #   Column                 Non-Null Count  Dtype         
---  ------                 --------------  -----         
 0   Datum                  2771 non-null   datetime64[us]
 1   Zeit                   2771 non-null   datetime64[us]
 2   Thermoelement 1 (°C)   2771 non-null   float64       
 3   Thermoelement 2 (°C)   2771 non-null   float64       
 4   Thermoelement 3 (°C)   2771 non-null   float64       
 5   Thermoelement 4 (°C)   2771 non-null   float64       
 6   Thermoelement 5 (°C)   2771 non-null   float64       
 7   Thermoelement 6 (°C)   2771 non-null   float64       
 8   Thermoelement 7 (°C)   2771 non-null   float64       
 9   Thermoelement 8 (°C)   2771 non-null   float64       
 10  Thermoelement 9 (°C)   2771 non-null   float64       
 11  Thermoelement 10 (°C)  2771 non-null   float64       
 12  Thermoelement 11 (°C)  2771 non-null   float64       
 13  Thermoelement 12 (°C)  2771 non-null   float64       
 14  Thermoelement 13 (°C)  2771 non-null   float64       
 15  Thermoelement 14 (°C)  2771 non-null   float64       
 16  Thermoelement 15 (°C)  2771 non-null   float64       
 17  Thermoelement 16 (°C)  2771 non-null   float64       
dtypes: datetime64[us](2), float64(16)
memory usage: 389.8 KB
None

Mit klimaschrank.describe() kann ein Überblick über die Daten gewonnen werden (hier aus Platzgründen für die ersten 4 Spalten).

print(klimaschrank.iloc[: , 0:4].describe())
                            Datum                        Zeit  \
count                        2771                        2771   
mean   2026-01-13 14:46:20.314688  2026-01-13 14:46:20.314688   
min           2026-01-13 10:55:17         2026-01-13 10:55:17   
25%    2026-01-13 12:50:48.500000  2026-01-13 12:50:48.500000   
50%           2026-01-13 14:46:21         2026-01-13 14:46:21   
75%           2026-01-13 16:41:52         2026-01-13 16:41:52   
max           2026-01-13 18:37:24         2026-01-13 18:37:24   
std                           NaN                         NaN   

       Thermoelement 1 (°C)  Thermoelement 2 (°C)  
count           2771.000000           2771.000000  
mean              59.761097             60.795669  
min               -9.100000            -11.400000  
25%               20.900000             20.950000  
50%               58.800000             60.400000  
75%               98.300000            100.200000  
max              138.700000            140.500000  
std               43.075393             44.310769  

Die Spalten Datum und Zeit enthalten die selben Informationen. Eine der beiden Spalten kann deshalb entfernt werden.

klimaschrank.drop(labels = 'Datum', axis = 1, inplace = True)

Betrachten wir den Verlauf der Daten für die ersten beiden Thermoelemente. (In der Darstellung mit pd.plot() ist die automatisch gewählte x-Achsenbeschriftung unansehnlich. Deshalb wird ein Datenobjekt für die Darstellung angelegt und die Spalte ‘Zeit’ auf die Uhrzeit reduziert.)

# Darstellung mit automatischer Achsenbeschriftung
# klimaschrank.iloc[: , 0:3].plot(x = 'Zeit', y = ['Thermoelement 1 (°C)', 'Thermoelement 2 (°C)'])

plotting_data = klimaschrank.copy()
plotting_data['Zeit'] = plotting_data['Zeit'].dt.time

plotting_data.plot(x = 'Zeit', y = ['Thermoelement 1 (°C)', 'Thermoelement 2 (°C)'], grid = True)
plt.ylabel(ylabel = '° C')
plt.show()

Darstellung der gemessenen Temperaturen der ersten beiden Thermoelemente über die Zeit.

 

Es ist zu erkennen, dass der Klimaschrank beim Aufheizen auf die nächste Zieltemperatur zunächst zu stark aufheizt, bevor die eingestellte Temperatur für einige Zeit konstant gehalten wird. Die Temperatur von 140 °C wird nur kurz erreicht, es liegen aber kaum Messwerte vor.

Bestimmen Sie nun den Nullpunktfehler für alle Thermoelemente.

  1. Wählen Sie für jedes Thermoelement die Bereiche der Datenreihe für die Temperaturstufen -10 bis 130 °C aus.
  2. Bestimmen Sie für jede Temperaturstufe die gemessene Temperatur im Bereich der Datenreihe, in der die Referenztemperatur konstant gehalten wird.
  3. Berechnen Sie den Nullpunktfehler für jeden Ausschnitt der Datenreihe und mitteln Sie die Ergebnisse für jedes Thermoelement.

Tipp 1: Lagemaße wie der Median, Mittelwert oder der Modus können eine einfacher zu bestimmende Schätzgröße für den Nullpunktfehler sein.

Tipp 2: Gehen Sie schrittweise vor: Versuchen Sie zunächst, eine Temperaturstufe eines Thermoelements zu isolieren und die gemessene Temperatur im konstanten Bereich der Datenreihe zu bestimmen. Schreiben Sie anschließend eine Funktion, um den Programmcode für mehrere Thermoelemente und Temperaturstufen zu wiederholen.

Tipp 3: Eine gleichermaßen für 13 Temperaturstufen und 16 Thermoelemente geeignete Lösung dürfte kaum zu finden sein. Manchmal wird ein Wert gesucht, manchmal liegt der gesuchte Wert zwischen zwei Messwerten. Eine geringfügige Unsicherheit kann durch die Automatisierung in Kauf genommen werden.

Lösung für ein Thermoelement.

  1. Bereich der Datenreihe für eine Temperatur auswählen, z. B. 0 °C. Dazu grenzen wir die Messwerte grob um den gesuchten Wert \(0 \pm 2 °C\) ein.
# Thermoelement 1
## Messwerte grob um den gesuchten Wert 0 °C eingrenzen
gesuchter_wert = 0

maske1 = klimaschrank['Thermoelement 1 (°C)'].le(gesuchter_wert + 2) # le = kleiner gleich
maske2 = klimaschrank['Thermoelement 1 (°C)'].ge(gesuchter_wert - 2) # ge = größer gleich

klimaschrank.loc[maske1 * maske2, 'Thermoelement 1 (°C)'].plot()
plt.xlabel(xlabel = 'Index')
plt.ylabel(ylabel = '°C')

plt.show()

Ausschnitt der Datenreihe von Thermoelement 1

 

In diesem Ausschnitt kann das Überschießen der Messwerte beobachtet werden, bevor sich die Messwerte etwa bei + 1 °C einpendeln. Auch ist der Anstieg zur folgenden Temperaturstufe zu erkennen.

  1. Im zweiten Schritt sollen die Daten im konstanten Temperaturbereich ausgewählt werden. Dazu soll ein möglichst einfaches Kriterium verwendet werden: der häufigste im Ausschnitt vorkommende Wert, also der Modus. Dazu wird die Methode pd.value_counts() verwendet, die eine absteigend sortierte Series der Häufigkeiten zurückgibt, wobei im Index die Werte gespeichert sind (siehe Beispiel). Der im Index gespeicherte häufigste Wert wird mit der Methode pd.idxmax() ausgelesen.

Die Ausgabe von pd.value_counts():

daten = pd.Series([1, 2, 3, 3, 3, 3, 3, 4, 4, 5])
daten.value_counts()
3    5
4    2
1    1
2    1
5    1
Name: count, dtype: int64
# Thermoelement 1

## Messwerte grob um den gesuchten Wert 0 °C eingrenzen
maske1 = klimaschrank['Thermoelement 1 (°C)'].le(gesuchter_wert + 2) # le = kleiner gleich
maske2 = klimaschrank['Thermoelement 1 (°C)'].ge(gesuchter_wert - 2) # ge = größer gleich

## häufigster Wert im eingegrenzten Temperaturbereich
modus_bereich = klimaschrank.loc[maske1 * maske2, 'Thermoelement 1 (°C)'].value_counts().idxmax()

## Nullpunktfehler bestimmen
nullpunktfehler = modus_bereich - gesuchter_wert
print(f"Nullpunktfehler: {nullpunktfehler}")

## plotten
klimaschrank.loc[maske1 * maske2, 'Thermoelement 1 (°C)'].plot()
plt.axhline(y = modus_bereich, color = 'r', linestyle = '--', label = 'Modus')
plt.xlabel(xlabel = 'Index')
plt.ylabel(ylabel = '°C')

plt.legend()
plt.show()
Nullpunktfehler: 0.9

 

Der Nullpunktfehler für Thermoelement 1 wird somit mit 0,9 °C bestimmt.

  1. Um die Berechnung für die verschiedenen Temperaturstufen zu automatisieren, schreiben wir eine Funktion.
# Eingabe: data = pd.Series, gesuchter_wert = array like, schwellwert, puffer = Skalar
# Verarbeitung: Elementweise werden die Werte in gesuchter_Wert ± puffer in data gesucht
# Verarbeitung: Die Differenz aus dem Modus jedes Datenausschnitts und dem gesuchten Wert wird als Nullpunktfehler interpretiert 
# Ausgabe: Wenn output = True wird DataFrame der gesuchten Werte und Nullpunktfehler ausgegeben, wenn output = False wird der mittlere Nullpunktefehler ausgegeben

def nullpunktfehler(data, gesuchte_werte, puffer = 2, output = False):

  # Zielobjekt und Zähler anlegen
  ausgabe = pd.DataFrame({'Gesuchter Wert': pd.Series(), 'Nullpunktfehler': pd.Series()})
  i = 0

  for gesuchter_wert in gesuchte_werte:
    
    ## Messwerte grob um den gesuchten Wert eingrenzen
    maske1 = data.le(gesuchter_wert + puffer) # le = kleiner gleich
    maske2 = data.ge(gesuchter_wert - puffer) # ge = größer gleich

    ## Kriterium häufigster Wert im eingegrenzten Temperaturbereich
    modus_bereich = data.loc[maske1 * maske2].value_counts().idxmax()
    
    # Nullpunktfehler bestimmen
    nullpunktfehler = modus_bereich - gesuchter_wert

    # Werte eintragen
    ausgabe.loc[i] = pd.Series([gesuchter_wert, nullpunktfehler]).values

    # Zähler erhöhen
    i += 1

  # Optional Ausgabe
  if output: # output is True
    print(ausgabe)

  # Rückgabe der gemittelten Nullpunktfehler
  return ausgabe['Nullpunktfehler'].mean()

Angewendet auf Thermoelement 1 (°C):

nullpunktfehler(data = klimaschrank['Thermoelement 1 (°C)'],
                gesuchte_werte = np.arange(-10, 140, 10),
                output = True)
    Gesuchter Wert  Nullpunktfehler
0            -10.0              1.5
1              0.0              0.9
2             10.0              0.7
3             20.0              0.5
4             30.0              0.0
5             40.0             -0.4
6             50.0             -0.9
7             60.0             -1.2
8             70.0             -1.7
9             80.0             -2.0
10            90.0             -2.0
11           100.0             -1.6
12           110.0             -1.8
13           120.0             -1.7
14           130.0             -1.6
np.float64(-0.7533333333333327)

Anwendung auf den Datensatz

Im letzten Schritt wenden wir die Funktion spaltenweise auf den DataFrame an.

# gibt pd.Series zurück
nullpunktfehler_klimaschrank = klimaschrank.iloc[: , 1:].apply(nullpunktfehler, axis = 0, gesuchte_werte = np.arange(-10, 140, 10))

print(nullpunktfehler_klimaschrank.round(3))
Thermoelement 1 (°C)    -0.753
Thermoelement 2 (°C)     0.027
Thermoelement 3 (°C)    -0.000
Thermoelement 4 (°C)     0.547
Thermoelement 5 (°C)     0.513
Thermoelement 6 (°C)    -0.233
Thermoelement 7 (°C)     0.073
Thermoelement 8 (°C)    -0.767
Thermoelement 9 (°C)    -0.373
Thermoelement 10 (°C)   -0.980
Thermoelement 11 (°C)   -0.900
Thermoelement 12 (°C)    0.513
Thermoelement 13 (°C)   -0.760
Thermoelement 14 (°C)   -0.593
Thermoelement 15 (°C)   -0.047
Thermoelement 16 (°C)   -0.987
dtype: float64

6.4 Multiplikative Fehler: Der Empfindlichkeitsfehler

Als Empfindlichkeits- oder Spannfehler wird ein über den Wertebereich (die Spanne) der gesuchten Größe zu- oder abnehmender Fehler bezeichnet. Die relative Messabweichung \(\delta\) ist dabei konstant, die absolute Messabweichung abhängig vom Messwert. (ics Schneider Messtechnik)

\[ \text{Messwerte} = \text{wahre Werte} \cdot \text{Empfindlichkeitsfehler} \]

# Daten erzeugen
messgröße = np.arange(0, 11)
messwerte = messgröße - messgröße / 3

# plotten
plt.plot(messgröße, marker = 'o', label = 'wahrer Wert')
plt.plot(messwerte, marker = '^', label = 'gemessener Wert')

# füllen
plt.fill_between(x = np.arange(messwerte.size), y1 = messgröße, y2 = messwerte, alpha = 0.2, label = 'Empfindlichkeitsfehler')

# Achsenbeschriftung
plt.xlabel('Index') 
plt.ylabel('Merkmalsausprägung')

plt.legend()
plt.show()

Schematische Darstellung des Empfindlichkeitsfehlers. Zwei Geraden der wahren und der gemessenen Werte verlaufen ausgehend von einem gemeinsamen Nullpunkt von links nach rechts mit zunehmend Abstand und sind am Punkt x = 0. Die Fläche zwischen den Geraden ist ausgefüllt - dies kennzeichnet den Empfindlichkeitsfehler.

Empfindlichkeitsfehler quantifizieren

Der Empfindlichkeitsfehler kann geschätzt werden, wenn Referenzwerte bekannt sind. Dazu wird das Verhältnis der Größenänderung (des Anstiegs) der gemessenen und der wahren Werte gebildet.

\[ \text{Empfindlichkeitsfehler} = \frac{\Delta \text{gemessene Werte}}{\Delta\text{wahre Werte}} \]

Mit den vollständigen Beispieldaten geht es leicht (in dem Beispiel wird eine Warnung wegen Division durch 0 unterdrückt):

print(f"Der Empfindlichkeitsfehler beträgt: {round((np.nanmean((messwerte / messgröße)) - 1 ) * 100, 2)} %")
Der Empfindlichkeitsfehler beträgt: -33.33 %

Bei nur punktuellen Daten wird eine ideale Kennlinie geschätzt, mit der der Empfindlichkeitsfehler geschätzt werden kann.

# bekannte Referenzwerte
x1 = 4
x2 = 8
y1 = messgröße[x1]
y2 = messgröße[x2]

# ideale Kennlinie schätzen
lm_kennlinie = poly.polyfit(x = [x1, x2], y = [y1, y2], deg = 1)
geschätzte_ideale_kennlinie = poly.polyval(x = np.arange(messwerte.size), c = lm_kennlinie)

# lineare Regression der Messwerte
lm_messwerte = poly.polyfit(x = np.arange(messwerte.size), y = messwerte, deg = 1)

# Berechnung Empfindlichkeitsfehler
spannfehler = lm_messwerte[1] / lm_kennlinie[1]

# Ausgabe
print(f"Der Anstieg der Messwerte: {round(lm_messwerte[1], 2)}")
print(f"Der Anstieg der idealen Kennlinie: {round(lm_kennlinie[1], 2)}")
print(f"Der Empfindlichkeitsfehler beträgt: {round((spannfehler - 1) * 100, 2)} %")
Der Anstieg der Messwerte: 0.67
Der Anstieg der idealen Kennlinie: 1.0
Der Empfindlichkeitsfehler beträgt: -33.33 %

Der Empfindlichkeitsfehler kann auch durch eine lineare Regression mit den bekannten Referenzwerten als unabhängige Größe \(x\) und den Messwerten als abhängige Größe \(y\) bestimmt werden. Der Anstieg der Regressionsgeraden ist der Empfindlichkeitsfehler.

# Empfindlichkeitsfehler bestimmen
lm_referenzpunkte = poly.polyfit(x = [messgröße[x1], messgröße[x2]], y = [messwerte[x1], messwerte[x2]], deg = 1)
spannfehler = lm_referenzpunkte[1]

print(f"Der Empfindlichkeitsfehler beträgt: {round((spannfehler - 1) * 100, 2)} %")
Der Empfindlichkeitsfehler beträgt: -33.33 %

Grafisch wird das Vorgehen deutlich.

# Punkte und Linien
plt.plot([0, 10], [messgröße[0], messgröße[-1]], marker = 'none', linestyle = 'dashed', color = 'C0', label = 'ideale Kennlinie')
plt.plot([x1, x2], [y1, y2], marker = 'o', linestyle = 'none', color = 'C0', label = 'bekannte Referenzwerte')
plt.plot(messwerte, marker = '^', color = 'C1', label = 'gemessene Werte')

# füllen
plt.fill_between(x = np.arange(messwerte.size), y1 = messgröße, y2 = messwerte, alpha = 0.2, label = 'Empfindlichkeitsfehler')

plt.legend()
plt.show()

Eine gestrichelte Linie verläuft ausgehend vom Schnittpunkt der Achsen durch zwei Referenzpunkte. Diese gestrichelte Linie enstpricht der idealen Kennlinie. Eine weitere Linie verbindet die gemessenen Werte. Der Abstand zwischen beiden Linien nimmt über den Wertebereich zu. Der Bereich zwischen den Linien ist farbig gefüllt und kennzeichnet den Empfindlichkeitsfehler

Empfindlichkeitsfehler korrigieren

Der Empfindlichkeitsfehler wird durch die Division der Messwerte durch den Empfindlichkeitsfehler korrigiert.

\[ \text{wahre Werte} = \frac{\text{Messwerte}}{\text{Empfindlichkeitsfehler}} \]

# Daten erzeugen
messgröße = np.arange(0, 11) + 5
messwerte = messgröße * 1.07

# bekannte Referenzpunkte 
x1 = 2
x2 = 5
y1 = messgröße[x1]
y2 = messgröße[x2]

# ideale Kennlinie schätzen
lm_kennlinie = poly.polyfit(x = [x1, x2], y = [y1, y2], deg = 1)

# lineare Funktion der Messwerte schätzen
lm_messwerte = poly.polyfit(x = np.arange(messwerte.size), y = messwerte, deg = 1)

# Empfindlichkeitsfehler bestimmen
spannfehler = lm_messwerte[1] / lm_kennlinie[1]
print(f"Der Empfindlichkeitsfehler beträgt: {( (spannfehler - 1) * 100).round(2)} %\n")

# Empfindlichkeitsfehler korrigieren
korrigierte_messwerte = messwerte / spannfehler

# Ausgabe
print(f"Wahre Werte:\n{messgröße}")
print(f"Messwerte:\n{messwerte}")
print(f"Korrigierte Messwerte:\n{korrigierte_messwerte}")
Der Empfindlichkeitsfehler beträgt: 7.0 %

Wahre Werte:
[ 5  6  7  8  9 10 11 12 13 14 15]
Messwerte:
[ 5.35  6.42  7.49  8.56  9.63 10.7  11.77 12.84 13.91 14.98 16.05]
Korrigierte Messwerte:
[ 5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]

Übung Empfindlichkeitsfehler

In einem Klimaschrank wurden 16 Thermoelemente einem Temperaturanstieg von -10 °C bis 140 °C ausgesetzt. Die Messgenauigkeit des verwendeten Datenloggers TCTempX16 liegt bei 0,01 °C, die Auflösung bei 0,1 °C. Für die Thermoelemente soll der Empfindlichkeitsfehler bestimmt werden.

Die Messdaten liegen in der Datei ‘01-daten/kalibration_tc_lineare_rampe.xlsx’. Die Daten können mit dem folgenden Befehl eingelesen werden.

klimaschrank = pd.read_excel(io = '01-daten/kalibration_tc_lineare_rampe.xlsx', sheet_name = 'T15949 MultiChannel - Daten', skiprows = 6)
print(klimaschrank.info())
<class 'pandas.DataFrame'>
RangeIndex: 2961 entries, 0 to 2960
Data columns (total 18 columns):
 #   Column                 Non-Null Count  Dtype         
---  ------                 --------------  -----         
 0   Datum                  2961 non-null   datetime64[us]
 1   Zeit                   2961 non-null   datetime64[us]
 2   Thermoelement 1 (°C)   2961 non-null   float64       
 3   Thermoelement 2 (°C)   2961 non-null   float64       
 4   Thermoelement 3 (°C)   2961 non-null   float64       
 5   Thermoelement 4 (°C)   2961 non-null   float64       
 6   Thermoelement 5 (°C)   2961 non-null   float64       
 7   Thermoelement 6 (°C)   2961 non-null   float64       
 8   Thermoelement 7 (°C)   2961 non-null   float64       
 9   Thermoelement 8 (°C)   2961 non-null   float64       
 10  Thermoelement 9 (°C)   2961 non-null   float64       
 11  Thermoelement 10 (°C)  2961 non-null   float64       
 12  Thermoelement 11 (°C)  2961 non-null   float64       
 13  Thermoelement 12 (°C)  2961 non-null   float64       
 14  Thermoelement 13 (°C)  2961 non-null   float64       
 15  Thermoelement 14 (°C)  2961 non-null   float64       
 16  Thermoelement 15 (°C)  2961 non-null   float64       
 17  Thermoelement 16 (°C)  2961 non-null   float64       
dtypes: datetime64[us](2), float64(16)
memory usage: 416.5 KB
None

Mit klimaschrank.describe() kann ein Überblick über die Daten gewonnen werden (hier aus Platzgründen für die ersten 4 Spalten).

print(klimaschrank.iloc[: , 0:4].describe())
                            Datum                        Zeit  \
count                        2961                        2961   
mean   2026-01-15 14:43:19.065856  2026-01-15 14:43:19.065856   
min           2026-01-15 10:36:25         2026-01-15 10:36:25   
25%           2026-01-15 12:39:52         2026-01-15 12:39:52   
50%           2026-01-15 14:43:19         2026-01-15 14:43:19   
75%           2026-01-15 16:46:46         2026-01-15 16:46:46   
max           2026-01-15 18:50:13         2026-01-15 18:50:13   
std                           NaN                         NaN   

       Thermoelement 1 (°C)  Thermoelement 2 (°C)  
count           2961.000000           2961.000000  
mean              79.988011             81.124688  
min               -8.500000            -10.700000  
25%               41.300000             42.400000  
50%               94.400000             96.200000  
75%              117.200000            118.800000  
max              147.100000            148.700000  
std               45.318636             46.109421  

Die Spalten Datum und Zeit enthalten die selben Informationen. Eine der beiden Spalten kann deshalb entfernt werden.

klimaschrank.drop(labels = 'Datum', axis = 1, inplace = True)

Betrachten wir den Verlauf der Daten für die ersten beiden Thermoelemente. (In der Darstellung mit pd.plot() ist die automatisch gewählte x-Achsenbeschriftung unansehnlich. Deshalb wird ein Datenobjekt für die Darstellung angelegt und die Spalte ‘Zeit’ auf die Uhrzeit reduziert.)

# Darstellung mit automatischer Achsenbeschriftung
# klimaschrank.iloc[: , 0:3].plot(x = 'Zeit', y = ['Thermoelement 1 (°C)', 'Thermoelement 2 (°C)'])

plotting_data = klimaschrank.copy()
plotting_data['Zeit'] = plotting_data['Zeit'].dt.time

plotting_data.plot(x = 'Zeit', y = ['Thermoelement 1 (°C)', 'Thermoelement 2 (°C)'], grid = True)
plt.ylabel(ylabel = '° C')
plt.show()

Darstellung der gemessenen Temperaturen der ersten beiden Thermoelemente über die Zeit.

 

Die Messung startet kurz vor der Aufheizphase. Etwa 16:40 Uhr wurde der Klimaschrank abgeschaltet. Die Messung lief danach noch einige Zeit weiter. Es müssen also der Start- und der Endpunkt der Aufheizphase bestimmt werden.

In diesem Beispiel soll angenommen werden, dass bei der Messung kein Nullpunktfehler vorliegt. Dazu suchen wir mit der Funktion aus Tipp 6.2 passende Messreihen. Aus dem anfänglich konstanten Bereich -10 °C wird der Nullpunktfehler bestimmt.

# gibt pd.Series zurück
nullpunktfehler_klimaschrank = klimaschrank.iloc[: , 1:].apply(nullpunktfehler, axis = 0, gesuchte_werte = [-10])

print(nullpunktfehler_klimaschrank)
Thermoelement 1 (°C)     2.0
Thermoelement 2 (°C)    -0.2
Thermoelement 3 (°C)     2.0
Thermoelement 4 (°C)     0.0
Thermoelement 5 (°C)    -0.1
Thermoelement 6 (°C)     1.8
Thermoelement 7 (°C)     0.1
Thermoelement 8 (°C)     1.7
Thermoelement 9 (°C)     1.6
Thermoelement 10 (°C)    1.7
Thermoelement 11 (°C)    1.8
Thermoelement 12 (°C)   -0.1
Thermoelement 13 (°C)    1.6
Thermoelement 14 (°C)    1.9
Thermoelement 15 (°C)    0.0
Thermoelement 16 (°C)    1.8
dtype: float64

Die Messreihen für Thermoelement 4 und Thermoelement 15 scheinen geeignet zu sein. Schauen wir uns die Messreihen einmal an:

# Thermoelement 4
gesuchter_wert = -10

## Messwerte grob um den gesuchten Wert 0 °C eingrenzen
maske1 = klimaschrank['Thermoelement 4 (°C)'].le(gesuchter_wert + 2) # le = kleiner gleich
maske2 = klimaschrank['Thermoelement 4 (°C)'].ge(gesuchter_wert - 2) # ge = größer gleich

## häufigster Wert im eingegrenzten Temperaturbereich
modus_bereich = klimaschrank.loc[maske1 * maske2, 'Thermoelement 4 (°C)'].value_counts().idxmax()

## Nullpunktfehler bestimmen
nullpunktfehler = modus_bereich - gesuchter_wert
print(f"Nullpunktfehler: {nullpunktfehler}")

## plotten
klimaschrank.loc[maske1 * maske2, 'Thermoelement 4 (°C)'].plot()
plt.axhline(y = modus_bereich, color = 'r', linestyle = '--', label = 'Modus')
plt.xlabel(xlabel = 'Index')
plt.ylabel(ylabel = '°C')

plt.legend()
plt.show()
Nullpunktfehler: 0.0

# Thermoelement 15
gesuchter_wert = -10

## Messwerte grob um den gesuchten Wert 0 °C eingrenzen
maske1 = klimaschrank['Thermoelement 15 (°C)'].le(gesuchter_wert + 2) # le = kleiner gleich
maske2 = klimaschrank['Thermoelement 15 (°C)'].ge(gesuchter_wert - 2) # ge = größer gleich

## häufigster Wert im eingegrenzten Temperaturbereich
modus_bereich = klimaschrank.loc[maske1 * maske2, 'Thermoelement 15 (°C)'].value_counts().idxmax()

## Nullpunktfehler bestimmen
nullpunktfehler = modus_bereich - gesuchter_wert
print(f"Nullpunktfehler: {nullpunktfehler}")

## plotten
klimaschrank.loc[maske1 * maske2, 'Thermoelement 15 (°C)'].plot()
plt.axhline(y = modus_bereich, color = 'r', linestyle = '--', label = 'Modus')
plt.xlabel(xlabel = 'Index')
plt.ylabel(ylabel = '°C')

plt.legend()
plt.show()
Nullpunktfehler: 0.0

Bestimmen Sie den Empfindlichkeitsfehler für Thermoelement 4 und Thermoelement 15. Das wahre Minimum der Temperatur betrage -10 °C und das wahre Maximum betrage 140 °C.

  1. Bestimmen Sie den Startpunkt der Aufheizphase.
  2. Bestimmen Sie den Endpunkt der Aufheizphase.
  3. Schätzen Sie eine ideale Kennlinie von -10 °C bis 140 °C für die Aufheizphase.
  4. Ermitteln und korrigieren Sie den Empfindlichkeitsfehler in den Daten.
  1. Startpunkt ermitteln, indem die Position des letzten Werts -10 °C bestimmt wird.
start4 = klimaschrank.loc[::-1, 'Thermoelement 4 (°C)'].eq(-10).idxmax()
start15 = klimaschrank.loc[::-1, 'Thermoelement 15 (°C)'].eq(-10).idxmax()
  1. Endpunkt ermitteln, indem die Position des Maximums bestimmt wird.
ende4 = klimaschrank.loc[:, 'Thermoelement 4 (°C)'].idxmax()
ende15 = klimaschrank.loc[:, 'Thermoelement 15 (°C)'].idxmax()

Ausgabe.

print(f"Messreihe Thermoelement 4 von Index {start4} bis Index {ende4}.",
      f"Messreihe Thermoelement 15 von Index {start15} bis Index {ende15}.",
      sep = '\n')
Messreihe Thermoelement 4 von Index 36 bis Index 2187.
Messreihe Thermoelement 15 von Index 36 bis Index 2188.
  1. Ideale Kennlinie und lineare Funktion der Messwerte schätzen, Empfindlichkeitsfehler bestimmen.
# bekannte Referenzpunkte 
x1 = start4
x2 = ende4
y1 = -10
y2 = 140

# ideale Kennlinie schätzen
lm_kennlinie = poly.polyfit(x = [x1, x2], y = [y1, y2], deg = 1)

# lineare Funktion der Messwerte schätzen
lm_messwerte = poly.polyfit(x = np.arange(x1, x2 + 1), y = klimaschrank.loc[x1 : x2, 'Thermoelement 4 (°C)'], deg = 1)

# Empfindlichkeitsfehler bestimmen
spannfehler = lm_messwerte[1] / lm_kennlinie[1]
print(f"Der Empfindlichkeitsfehler beträgt: {( ((lm_messwerte[1] / lm_kennlinie[1]) - 1) * 100).round(2)} %")

# Empfindlichkeitsfehler korrigieren
klimaschrank.loc[ : , 'Thermoelement 4 (°C)'] = klimaschrank.loc[ : , 'Thermoelement 4 (°C)'].div(spannfehler)
Der Empfindlichkeitsfehler beträgt: 6.66 %
# bekannte Referenzpunkte 
x1 = start15
x2 = ende15
y1 = -10
y2 = 140

# ideale Kennlinie schätzen
lm_kennlinie = poly.polyfit(x = [x1, x2], y = [y1, y2], deg = 1)

# lineare Funktion der Messwerte schätzen
lm_messwerte = poly.polyfit(x = np.arange(x1, x2 + 1), y = klimaschrank.loc[x1 : x2, 'Thermoelement 15 (°C)'], deg = 1)

# Empfindlichkeitsfehler bestimmen
spannfehler = lm_messwerte[1] / lm_kennlinie[1]
print(f"Der Empfindlichkeitsfehler beträgt: {( ((lm_messwerte[1] / lm_kennlinie[1]) - 1) * 100).round(2)} %")

# Empfindlichkeitsfehler korrigieren
klimaschrank.loc[ : , 'Thermoelement 15 (°C)'] = klimaschrank.loc[ : , 'Thermoelement 15 (°C)'].div(spannfehler)
Der Empfindlichkeitsfehler beträgt: 5.62 %

6.5 Nullpunkt- und Empfindlichkeitsfehler

Absolute und multiplikative Abweichungen können gemeinsam auftreten.

\[ \text{Messwerte} = \text{wahre Werte} \cdot Empfindlichkeitsfehler + \text{Nullpunktfehler} \]

In diesem Beispiel wird die absolute Messabweichung über den dargestellten Wertebereich kleiner, da der Nullpunktfehler mit positivem Vorzeichen und der Empfindlichkeitsfehler mit negativen Vorzeichen auftreten.

Schematische Darstellung einer Messreihe mit Nullpunkt- und Empfindlichkeitsfehler. Zwei Geraden der wahren und der gemessenen Werte verlaufen von links nach rechts mit abnehmenden Abstand und sind am Punkt x = 0 durch eine gestrichelte Linie verbunden (Nullpunktfehler). Die Fläche zwischen den Geraden ist ausgefüllt - dies kennzeichnet den Empfindlichkeitsfehler.

# Daten erzeugen
messgröße = np.arange(0, 11)
messwerte = messgröße + 4 - messgröße / 3

# plotten
plt.plot(messgröße, marker = 'o', label = 'wahrer Wert')
plt.plot(messwerte, marker = '^', label = 'gemessener Wert')
plt.plot([0, 0], [messgröße[0], messwerte[0]], linestyle = 'dashed', label = 'Nullpunktfehler')

# füllen
hilfslinie_spannfehler = messwerte - 4
plt.fill_between(x = np.arange(messwerte.size), y1 = messgröße, y2 = hilfslinie_spannfehler, alpha = 0.2, label = 'Empfindlichkeitsfehler')

# Nullpunktfehler abtragen
plt.plot([4, 4], [hilfslinie_spannfehler[4] , messwerte[4]], linestyle = 'dashed', color = 'red')
plt.plot([8, 8], [hilfslinie_spannfehler[8], messwerte[8]], linestyle = 'dashed', color = 'red')

# Achsenbeschriftung
plt.xlabel('Index') 
plt.ylabel('Merkmalsausprägung')

plt.legend()
plt.show()

Nullpunkt- und Empfindlichkeitsfehler quantifizieren

Aus mindestens zwei bekannten Referenzpunkten können mit linearer Regression der Nullpunkt- und der Empfindlichkeitsfehler geschätzt werden.

# Daten erzeugen
messgröße = np.arange(0, 11) * 2 + 7
messwerte = messgröße + 24 - messgröße / 3

# bekannte Referenzpunkte
x1 = 4
x2 = 8
y1 = messgröße[x1]
y2 = messgröße[x2]

# lineare Regression mit x = Referenzwerte und y = Messwerte
lm_referenzpunkte = poly.polyfit(x = [messgröße[x1], messgröße[x2]], y = [messwerte[x1], messwerte[x2]], deg = 1)
print(lm_referenzpunkte, "\n")

## Nullpunktfehler bestimmen
nullpunktfehler = lm_referenzpunkte[0]
print(f"Der Nullpunktfehler beträgt: {nullpunktfehler.round(2)}")

## Empfindlichkeitsfehler bestimmen
spannfehler = lm_referenzpunkte[1]
print(f"Der Empfindlichkeitsfehler beträgt: {((spannfehler - 1) * 100).round(2)} %")

## Alternativ: Empfindlichkeitsfehler bestimmen
### ideale Kennlinie schätzen
lm_kennlinie = poly.polyfit(x = [x1, x2], y = [y1, y2], deg = 1)
# print(f"lm_kennlinie: {lm_kennlinie}")

###lineare Regression der Messwerte
lm_messwerte = poly.polyfit(x = np.arange(messwerte.size), y = messwerte, deg = 1)
# print(f"lm_messwerte: {lm_messwerte}")

### Empfindlichkeitsfehler bestimmen
spannfehler = lm_messwerte[1] / lm_kennlinie[1]
print(f"Der Empfindlichkeitsfehler beträgt: {((spannfehler - 1) * 100).round(2)} %")
[24.          0.66666667] 

Der Nullpunktfehler beträgt: 24.0
Der Empfindlichkeitsfehler beträgt: -33.33 %
Der Empfindlichkeitsfehler beträgt: -33.33 %

Nullpunkt- und Empfindlichkeitsfehler korrigieren

Bei der Korrektur von Nullpunkt- und Empfindlichkeitsfehler unterscheidet sich das Vorgehen abhängig von der Reihenfolge der vorgenommenen Korrekturen.

  1. Zuerst wird der Nullpunktfehler korrigiert, danach der Empfindlichkeitsfehler.

\[ korrigierte ~ Messwerte = \frac{(Messwerte - Nullpunktfehler)}{Empfindlichkeitsfehler} \]

  1. Zuerst wird der Empfindlichkeitsfehler korrigiert, danach der Nullpunktfehler.

\[ korrigierte ~ Messwerte = \frac{Messwerte}{Empfindlichkeitsfehler} - \frac{Nullpunktfehler}{Empfindlichkeitsfehler} \]

Ein Beispiel:

# Daten erzeugen
messgröße = np.arange(0, 11) * 2 + 7
messwerte = messgröße + 4 - messgröße / 3

# bekannte Referenzpunkte
x1 = 4
x2 = 8
y1 = messgröße[x1]
y2 = messgröße[x2]

# Ausgabe
print(f"Wahre Werte:\n{messgröße}")
print(f"Messwerte:\n{messwerte}")
print()

# ideale Kennlinie schätz# lineare Regression mit x = Referenzwerte und y = Messwerte
lm_referenzpunkte = poly.polyfit(x = [messgröße[x1], messgröße[x2]], y = [messwerte[x1], messwerte[x2]], deg = 1)

## Nullpunktfehler bestimmen
nullpunktfehler = lm_referenzpunkte[0]
print(f"Der Nullpunktfehler beträgt: {nullpunktfehler.round(2)}")

## Empfindlichkeitsfehler bestimmen
spannfehler = lm_referenzpunkte[1]
print(f"Der Empfindlichkeitsfehler beträgt: {spannfehler.round(2)}")
print()

# erst Nullpunktfehler, dann Empfindlichkeitsfehler korrigieren
korrigierte_messwerte = messwerte - nullpunktfehler
korrigierte_messwerte = korrigierte_messwerte / spannfehler
print(f"erst Nullpunktfehler, dann Empfindlichkeitsfehler korrigiert:\n{korrigierte_messwerte}")

# erst Empfindlichkeitsfehler, dann Nullpunkt korrigieren
korrigierte_messwerte = messwerte / spannfehler
korrigierte_messwerte = korrigierte_messwerte - (nullpunktfehler / spannfehler)
print(f"erst Empfindlichkeitsfehler, dann Nullpunktfehler korrigiert:\n{korrigierte_messwerte}")
Wahre Werte:
[ 7  9 11 13 15 17 19 21 23 25 27]
Messwerte:
[ 8.66666667 10.         11.33333333 12.66666667 14.         15.33333333
 16.66666667 18.         19.33333333 20.66666667 22.        ]

Der Nullpunktfehler beträgt: 4.0
Der Empfindlichkeitsfehler beträgt: 0.67

erst Nullpunktfehler, dann Empfindlichkeitsfehler korrigiert:
[ 7.  9. 11. 13. 15. 17. 19. 21. 23. 25. 27.]
erst Empfindlichkeitsfehler, dann Nullpunktfehler korrigiert:
[ 7.  9. 11. 13. 15. 17. 19. 21. 23. 25. 27.]

6.6 Nichtlinearität: Linearitätsfehler

Bei einer idealen Messung hängt der gesuchte Wert linear vom gemessenen Wert ab. Viele analoge Sensoren reagieren aufgrund von Materialeigenschaften oder abhängig von der Temperatur nicht linear auf die gemessene Größe. Das heißt, die Messwerte sind nicht direkt proportional zur Messgröße.

Dem kann zum einen durch die Anwendung von Kalibriermethoden wie der Korrektionstabelle oder von nicht linearen Verfahren zur Parameterschätzung, die im Methodenbaustein Datenfitting und Datenoptimierung behandelt werden, begegnet werden. Zum anderen können die Daten linear approximiert werden, um mit den leichter zu handhabenden linearen Verfahren der Parameterschätzung arbeiten zu können. Der dabei auftretende Linearitätsfehler muss quantifiziert werden. Dieses Vorgehen wird hier vorgestellt.

Beispiel Pt100

Das Pt100 ist ein Platin-Widerstandsthermometer (Pt = Platin) mit einem definierten Widerstandswert von \(100 \Omega\) bei einer Temperatur von 0°C (daher der Name Pt100). Der Widerstand eines Pt100 steigt mit der Temperatur. Bei 100 °C beträgt der Widerstand beispielsweise \(138,51 \Omega\). Der Zusammenhang zwischen der Eingangsgröße, dem elektrischen Widerstand, und der so gemessenen Temperatur ist jedoch nur näherungsweise linear.

Die Eigenschaften eines Pt100 Widerstandes sind in der Norm DIN EN IEC 60751 (Deutsches Institut für Normung 2023) festgelegt. Dort wird der Zusammenhang durch zwei Polynome für den Temperaturbereich von -200 °C bis 0 °C und für den Temperaturbereich von 0 °C bis 850 °C beschrieben.

Für den Temperaturbereich –200 °C bis 0 °C: \[ R_T = R_0 \cdot \left(1 + A \cdot T + B \cdot T^2 + C \cdot (T - 100 ^\circ\text{C}) \cdot T^3\right) \]

Für den Temperaturbereich von 0 °C bis +850 °C: \[ R_T = R_0 \cdot \left(1 + A \cdot T + B \cdot T^2\right) \]

Dabei gilt:

  • \(R_T\) ist der Widerstand bei der Temperatur \(T\),

  • \(R_0\) ist der Widerstand bei \(0^\circ\text{C}\),

  • \(A\), \(B\) und \(C\) sind Konstanten, die den spezifischen Charakter des Sensors beschreiben. Dabei sind:

    • \(A = 3.9083 \times 10^{-3} ~ {^\circ\text{C}}^{-1}\)
    • \(B = -5.775 \times 10^{-7} ~ {^\circ\text{C}}^{-2}\)
    • \(C = -4.183 \times 10^{-12} ~ {^\circ\text{C}}^{-4}\)

(Deutsches Institut für Normung 2023, 13)

Im Anhang der Norm befinden sich Tabellen, die die Beziehung zwischen Temperatur und gemessenem Widerstand wiedergeben. Das Format der Tabellen erlaubt es, aus einem gemessenen Widerstandswert schnell die gemessene Temperatur zu ermitteln. Dazu sind in der ersten Spalte die Temperaturen in Zehnerschritten, in den folgenden zehn Spalten die Einerstelle eingetragen. Für Temperaturen unter Null ist die Einerstelle jeweils zu subtrahieren (erkennbar am Vorzeichen \(-\)), für Temperaturen über Null dagegen zu addieren (erkennbar am Vorzeichen \(+\)). Eine zusammengefasste Darstellung finden Sie zum Beispiel hier.

Ausschnitt der Tabelle A.1. Für die Temperaturen von -200 bis -170 Grad Celsius sind in 1-Grad-Schritten die von einem Pt100 gemessenen Widerstandswerte eingetragen.
Abbildung 6.1: Ausschnitt der Tabelle A.1

(Deutsches Institut für Normung 2023, 25)

Die Notation \(t_{90} / °C\) beruht auf der Internationalen Temperaturskala ITS-90 von 1990, die Temperaturen \(T_{90}\) in Kelvin und \(t_{90}\) in Grad Celsius definiert. Die Notation zeigt also an, dass in der Tabelle Angaben in Grad Celsius stehen. (Deutsches Institut für Normung (2023), S. 10)

Für die Datenanalyse ist das tabellarische Format weniger geeignet. Die hier zusammengefasste Darstellung wurde in zwei CSV-Dateien kopiert.

dateipfad_belowzero = '01-daten/pt100-table-below-zero.csv'
dateipfad_abovezero = '01-daten/pt100-table-above-zero.csv'

Die Dateien soll so eingelesen werden, dass der elektrische Widerstand und die Temperatur jeweils eine aufsteigende Datenreihe bilden. Zunächst betrachten wir die Datei mit mit Temperaturen unter 0 Grad Celsius.

# belowzero
belowzero = pd.read_csv(filepath_or_buffer = dateipfad_belowzero, sep = ',')

print(belowzero.head(), "\n")
print(belowzero.tail())
   Temperatur in °C Unnamed: 1 Unnamed: 2 Unnamed: 3 Unnamed: 4 Unnamed: 5  \
0               NaN          0         -1         -2         -3         -4   
1            -200.0      18,52        NaN        NaN        NaN        NaN   
2            -190.0      22,83       22,4      21,97      21,54      21,11   
3            -180.0       27,1      26,67      26,24      25,82      25,39   
4            -170.0      31,34      30,91      30,49      30,07      29,64   

  Unnamed: 6 Unnamed: 7 Unnamed: 8 Unnamed: 9 Unnamed: 10  
0         -5         -6         -7         -8          -9  
1        NaN        NaN        NaN        NaN         NaN  
2      20,68      20,25      19,82      19,38       18,95  
3      24,97      24,54      24,11      23,68       23,25  
4      29,22       28,8      28,37      27,95       27,52   

    Temperatur in °C Unnamed: 1 Unnamed: 2 Unnamed: 3 Unnamed: 4 Unnamed: 5  \
17             -40.0      84,27      83,87      83,48      83,08      82,69   
18             -30.0      88,22      87,83      87,43      87,04      86,64   
19             -20.0      92,16      91,77      91,37      90,98      90,59   
20             -10.0      96,09      95,69       95,3      94,91      94,52   
21               0.0        100      99,61      99,22      98,83      98,44   

   Unnamed: 6 Unnamed: 7 Unnamed: 8 Unnamed: 9 Unnamed: 10  
17      82,29      81,89       81,5       81,1        80,7  
18      86,25      85,85      85,46      85,06       84,67  
19      90,19       89,8       89,4      89,01       88,62  
20      94,12      93,73      93,34      92,95       92,55  
21      98,04      97,65      97,26      96,87       96,48  

Es gibt 201 Werte (von -200 bis 0). Die Daten beginnen in der Zeile mit dem Index 1 (wenn die erste Zeile als header eingelesen wird). Diese enthält nur einen Eintrag in der Spalte ‘0’. Die folgenden Zeilen sind von rechts nach links einzulesen. Das Dezimaltrennzeichen ist das ,. Das Einlesen der Daten von rechts nach links kann auf viele Arten bewerkstelligt werden. Eine einzeilige Lösung beginnt damit, der Methode pd.iloc[::-1] eine negative Schrittweite zu übergeben.

df = pd.DataFrame(np.array([[3, 2, 1], [6, 5, 4]]))
print(df, "\n")
print(df.iloc[::-1])
   0  1  2
0  3  2  1
1  6  5  4 

   0  1  2
1  6  5  4
0  3  2  1

Das führt dazu, dass die Zeilen des DataFrame in umgekehrter Reihenfolge ausgegeben werden. Um die Spalten in umgekehrter Reihenfolge auszugeben, wird der DataFrame mit der Methode pd.T zwei mal transponiert.

print(df.T.iloc[::-1].T)
   2  1  0
0  1  2  3
1  4  5  6

Mit der NumPy-Methode np.flatten() kann ein Array in eine eindimensionale Struktur reduziert werden. Dafür wird mit der Methode pd.to_numpy() der DataFrame als NumPy-Array ausgegeben.

print(df.T.iloc[::-1].T.to_numpy())
print() # leere Zeile
print(df.T.iloc[::-1].T.to_numpy().flatten())
[[1 2 3]
 [4 5 6]]

[1 2 3 4 5 6]

Versuchen wir es mit dem Kopf der Pt100-Daten.

belowzero = pd.read_csv(filepath_or_buffer = dateipfad_belowzero, sep = ',', decimal = ',', header = 0, skiprows = 1, index_col = 0)

print(belowzero.head())
print() # leere Zeile
print(belowzero.head().T.iloc[::-1].T.to_numpy().flatten())
          0     -1     -2     -3     -4     -5     -6     -7     -8     -9
-200  18.52    NaN    NaN    NaN    NaN    NaN    NaN    NaN    NaN    NaN
-190  22.83  22.40  21.97  21.54  21.11  20.68  20.25  19.82  19.38  18.95
-180  27.10  26.67  26.24  25.82  25.39  24.97  24.54  24.11  23.68  23.25
-170  31.34  30.91  30.49  30.07  29.64  29.22  28.80  28.37  27.95  27.52
-160  35.54  35.12  34.70  34.28  33.86  33.44  33.02  32.60  32.18  31.76

[  nan   nan   nan   nan   nan   nan   nan   nan   nan 18.52 18.95 19.38
 19.82 20.25 20.68 21.11 21.54 21.97 22.4  22.83 23.25 23.68 24.11 24.54
 24.97 25.39 25.82 26.24 26.67 27.1  27.52 27.95 28.37 28.8  29.22 29.64
 30.07 30.49 30.91 31.34 31.76 32.18 32.6  33.02 33.44 33.86 34.28 34.7
 35.12 35.54]

Um die fehlenden Werte zu überspringen, wandeln wir das bisherige Ergebnis wieder in eine pd.Series() um und verwenden die Methode pd.Series.dropna().

pd.Series(belowzero.head().T.iloc[::-1].T.to_numpy().flatten()).dropna()
9     18.52
10    18.95
11    19.38
12    19.82
13    20.25
14    20.68
15    21.11
16    21.54
17    21.97
18    22.40
19    22.83
20    23.25
21    23.68
22    24.11
23    24.54
24    24.97
25    25.39
26    25.82
27    26.24
28    26.67
29    27.10
30    27.52
31    27.95
32    28.37
33    28.80
34    29.22
35    29.64
36    30.07
37    30.49
38    30.91
39    31.34
40    31.76
41    32.18
42    32.60
43    33.02
44    33.44
45    33.86
46    34.28
47    34.70
48    35.12
49    35.54
dtype: float64

Und jetzt für die gesamte Datei:

belowzero_ohm = pd.Series(belowzero.T.iloc[::-1].T.to_numpy().flatten()).dropna()
print(belowzero_ohm.head(), belowzero_ohm.tail(), sep = '\n')
9     18.52
10    18.95
11    19.38
12    19.82
13    20.25
dtype: float64
205     98.44
206     98.83
207     99.22
208     99.61
209    100.00
dtype: float64

Die Temperatur können wir einfach durch ein range-Objekt erzeugen:

belowzero_temperatur = pd.Series(range(-200, 1)) # range stop ist exklusiv
print(belowzero_temperatur.head(), belowzero_temperatur.tail(), sep = '\n')
0   -200
1   -199
2   -198
3   -197
4   -196
dtype: int64
196   -4
197   -3
198   -2
199   -1
200    0
dtype: int64

Für die Temperaturen oberhalb von 0 Grad kann das Vorgehen vereinfacht werden, da die Datei gleich aufgebaut ist und die Werte aufsteigend sortiert sind.

abovezero = pd.read_csv(filepath_or_buffer = dateipfad_abovezero, sep = ',', decimal = ',', header = 0, skiprows = 1, index_col = 0)

abovezero_ohm = pd.Series(abovezero.to_numpy().flatten()).dropna()
print(abovezero_ohm.head(), abovezero_ohm.tail(), sep = '\n')
0    100.00
1    100.39
2    100.78
3    101.17
4    101.56
dtype: float64
846    389.31
847    389.60
848    389.90
849    390.19
850    390.48
dtype: float64

Beide Datensätze enthalten einen Eintrag für die Temperatur 0 Grad, sodass dieser in der zweiten Datei entfernt werden kann.

abovezero_ohm = abovezero_ohm[1:]
print(abovezero_ohm.head(), abovezero_ohm.tail(), sep = '\n')
1    100.39
2    100.78
3    101.17
4    101.56
5    101.95
dtype: float64
846    389.31
847    389.60
848    389.90
849    390.19
850    390.48
dtype: float64

Die Temperatur wird wieder durch ein range-Objekt erzeugt.

abovezero_temperatur = pd.Series(range(1, 851)) # range stop ist exklusiv

Die aus beiden Datensätzen erzeugten Series können zusammengefasst werden:

pt100 = pd.DataFrame({'Temperatur': pd.concat([belowzero_temperatur, abovezero_temperatur], ignore_index = True), 'Ohm': pd.concat([belowzero_ohm, abovezero_ohm], ignore_index = True)})
print(pt100.head(), pt100.tail(), sep = '\n')

print(pt100.info())
   Temperatur    Ohm
0        -200  18.52
1        -199  18.95
2        -198  19.38
3        -197  19.82
4        -196  20.25
      Temperatur     Ohm
1046         846  389.31
1047         847  389.60
1048         848  389.90
1049         849  390.19
1050         850  390.48
<class 'pandas.DataFrame'>
RangeIndex: 1051 entries, 0 to 1050
Data columns (total 2 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   Temperatur  1051 non-null   int64  
 1   Ohm         1051 non-null   float64
dtypes: float64(1), int64(1)
memory usage: 16.6 KB
None

Nichtlinearität darstellen

Mit den Daten aus Beispiel 6.2 kann die Nichtlinearität des Sensors dargestellt werden.

plt.plot(pt100['Temperatur'], pt100['Ohm'], marker = 'o', linestyle = '', label = 'elektrischer Widerstand', alpha = 0.6)
plt.plot([pt100['Temperatur'].iloc[0], pt100['Temperatur'].iloc[-1]], [pt100['Ohm'].iloc[0], pt100['Ohm'].iloc[-1]], linewidth = 2, label = 'Referenzlinie')
plt.yticks(pt100['Ohm'][::50]);

plt.xlabel('Temperatur in °C')
plt.ylabel('elektrischer Widerstand in Ohm')
plt.grid()
plt.legend()

plt.show()

Darstellung der Temperatur in Grad Celsius auf der x-Achse und des elektrischen Widerstands in Ohm auf der y-Achse

 

Es fällt auf, dass die Daten einen Fehlwert enthalten. Die Position des Fehlwerts wird mit zwei Pandas-Methoden bestimmt. pd.diff() gibt die Differenz jedes Werts zu seinem Vorgänger zurück. pd.idxmin() gibt den Zeilenindex (genauer das label) des kleinsten Werts zurück. (Läge der Fehlwert oberhalb der Linie, würde pd.idxmax() verwendet werden.)

 # := ist der sog. Walross-Operator
print(( position_fehlwert := pt100['Ohm'].diff().idxmin() ))
print(pt100.iloc[list(range(position_fehlwert - 2, position_fehlwert + 3))])
341
     Temperatur     Ohm
339         139  153.21
340         140  153.58
341         141   13.96
342         142  154.33
343         143  154.71

Der korrekte Wert kann aus der DIN-Norm (Deutsches Institut für Normung 2023, 26) abgelesen werden (153,96). Man könnte den Wert aber auch interpolieren (siehe Beispiel).

Die einfachste Form der Interpolation ist die lineare Interpolation.

interpolate_me = pt100.loc[position_fehlwert - 1 : position_fehlwert + 1, 'Ohm'].copy()
interpolate_me[position_fehlwert] = np.nan
print(interpolate_me, "\n")

print("linear interpolierter Wert:", interpolate_me.interpolate(), sep = "\n")
340    153.58
341       NaN
342    154.33
Name: Ohm, dtype: float64 

linear interpolierter Wert:
340    153.580
341    153.955
342    154.330
Name: Ohm, dtype: float64

Durch Runden wird das korrekte Ergebnis erreicht (darauf verlassen sollte man sich aber nicht).

print("linear interpolierter Wert:", interpolate_me.interpolate().round(2), sep = "\n")
korrekter_wert = interpolate_me.interpolate().round(2)[position_fehlwert]
linear interpolierter Wert:
340    153.58
341    153.96
342    154.33
Name: Ohm, dtype: float64

Eine andere Option ist die nichtlineare Interpolation (die nicht Inhalt dieses Bausteins ist).

Die nichtlineare Interpolation benötigt mehr Datenpunkte. Wir versuchen die kubische Interpolation:

interpolate_me_again = pt100.loc[position_fehlwert - 3 : position_fehlwert + 4, 'Ohm'].copy()
interpolate_me_again[position_fehlwert] = np.nan
print("polynomial interpolierter Wert:", interpolate_me_again.interpolate(method = 'polynomial', order = 3), sep = "\n")
polynomial interpolierter Wert:
338    152.830000
339    153.210000
340    153.580000
341    153.952213
342    154.330000
343    154.710000
344    155.080000
345    155.460000
Name: Ohm, dtype: float64

Das Ergebnis ist ungenauer. Warum das so ist, erfahren Sie im Methodenbaustein Datenfitting und Datenoptimierung.

Der Fehlwert (dessen Position in obigem Beispiel bestimmt wird) wird korrigiert:

pt100.loc[position_fehlwert, 'Ohm'] = korrekter_wert
print(pt100.loc[position_fehlwert - 1 : position_fehlwert + 1, 'Ohm'])
340    153.58
341    153.96
342    154.33
Name: Ohm, dtype: float64

Sodann kann die Nichtlinearität erneut dargestellt werden:

plt.plot(pt100['Temperatur'], pt100['Ohm'], marker = 'o', linestyle = '', label = 'elektrischer Widerstand', alpha = 0.6)
plt.plot([pt100['Temperatur'].iloc[0], pt100['Temperatur'].iloc[-1]], [pt100['Ohm'].iloc[0], pt100['Ohm'].iloc[-1]], linewidth = 2, label = 'Referenzlinie')
plt.yticks(pt100['Ohm'][::50]);

plt.xlabel('Temperatur in °C')
plt.ylabel('elektrischer Widerstand in Ohm')
plt.grid()
plt.legend()

plt.show()

Darstellung der Temperatur in Grad Celsius auf der x-Achse und des elektrischen Widerstands in Ohm auf der y-Achse

Nichtlinearität quantifizieren

Zwei Methoden zur Quantifizierung der Nichtliniearität sind die Festpunkt- und die Toleranzbandmethode.

Festpunktmethode

Definition 6.3: Festpunktmethode

Die Endpunkte der realen Kennlinie werden durch eine Gerade verbunden. Der Linearitätsfehler ist das Maximum der Abweichung der Kennlinie zu dieser Geraden. (Grote und Feldhusen 2011, W2–3 (S. 1661-1662))

Dazu bestimmen wir zunächst die Vorhersagewerte einer Geraden durch die Endpunkte.

lm = poly.polyfit(
  x = [pt100['Temperatur'].iloc[0], pt100['Temperatur'].iloc[-1]],
  y = [pt100['Ohm'].iloc[0], pt100['Ohm'].iloc[-1]],
  deg = 1)
print(lm.round(2)) # intercept + slope

vorhersagewerte = poly.polyval(x = pt100['Temperatur'], c = lm)

plt.plot(pt100['Temperatur'], pt100['Ohm'], marker = 'o', linestyle = '', label = 'elektrischer Widerstand', alpha = 0.6)
plt.plot(pt100['Temperatur'], vorhersagewerte, linewidth = 2, label = 'Vorhersagewerte endpunktverbindende Gerade')
plt.yticks(pt100['Ohm'][::50]);

plt.xlabel('Temperatur in °C')
plt.ylabel('elektrischer Widerstand in Ohm')
plt.grid()
plt.legend()

plt.show()
[89.37  0.35]

Darstellung der Temperatur in Grad Celsius auf der x-Achse und des elektrischen Widerstands in Ohm auf der y-Achse

 

Aus der Differenz des gemessenen elektrischen Widerstands und der linearen Vorhersagewerte kann der maximale Linearitätsfehler bestimmt werden.

linearitätsfehler_festpunkt = (pt100['Ohm'] - vorhersagewerte).abs().max()
print(f"Linearitätsfehler nach Festpunktmethode: {linearitätsfehler_festpunkt:.2f} Ohm.")
Linearitätsfehler nach Festpunktmethode: 16.43 Ohm.

Toleranzbandmethode

Definition 6.4: Toleranzbandmethode

Bei der Toleranzbandmethode wird eine Gerade so durch die Messpunkte gelegt, dass die Summe der quadrierten Abweichungen der Messpunkte zu dieser Geraden (Methode der kleinsten Quadrate) oder die größte einzelne Abweichung (Tschebyscheff-Approximation) minimiert wird. Die Größe des Linearitätsfehlers ist die maximale senkrechte Entfernung der Kennlinie zu dieser Ausgleichsgeraden. (Grote und Feldhusen 2011, W3 (S. 1662))

Das Prinzip der Methode der kleinsten Quadrate haben wir bereits kennengelernt.

lm = poly.polyfit(
  x = pt100['Temperatur'],
  y = pt100['Ohm'],
  deg = 1)
print(lm.round(2)) # intercept + slope

vorhersagewerte = poly.polyval(x = pt100['Temperatur'], c = lm)

plt.plot(pt100['Temperatur'], pt100['Ohm'], marker = 'o', linestyle = '', label = 'elektrischer Widerstand', alpha = 0.6)
plt.plot(pt100['Temperatur'], vorhersagewerte, linewidth = 2, label = 'Regressionsgerade')
plt.yticks(pt100['Ohm'][::50]);

plt.xlabel('Temperatur in °C')
plt.ylabel('elektrischer Widerstand in Ohm')
plt.grid()
plt.legend()

plt.show()
[100.67   0.35]

Darstellung der Temperatur in Grad Celsius auf der x-Achse und des elektrischen Widerstands in Ohm auf der y-Achse

 

Aus der Differenz des gemessenen elektrischen Widerstands und der linearen Vorhersagewerte kann der maximale Linearitätsfehler (= das größte Residuum) bestimmt werden.

linearitätsfehler_toleranzband = (pt100['Ohm'] - vorhersagewerte).abs().max()
print(f"Linearitätsfehler nach Toleranzbandmethode: {linearitätsfehler_toleranzband:.2f} Ohm.")
Linearitätsfehler nach Toleranzbandmethode: 11.45 Ohm.

6.7 Die Zweipunktkalibrierung

Die Zweipunktkalibrierung ist ein praktisches Verfahren für die Kalibrierung von Messdaten. Die Zweipunktkalibrierung erfolgt über das bekannte wahre Minimum und das bekannte wahre Maximum einer Messreihe.

\[ a = \frac{(\text{wahres Maximum} - \text{wahres Minimum})}{(\text{Maximum Messwerte} - \text{Minimum Messwerte})} \]

\[ b = \text{wahres Minimum} - a \cdot \text{Minimum Messwerte} \]

\[ \text{kalibrierte Messwerte} = a \cdot \text{Messwerte} + b \]

Die Variable \(a\) ist der Korrekturfaktor für den Empfindlichkeitsfehler. Die Variable \(b\) ist die Nullpunktkorrektur, nachdem der Empfindlichkeitsfehler korrigiert wurde.

# Daten erzeugen
messgröße = np.arange(0, 11) + 7
messwerte = messgröße + 4 - messgröße / 3

# bekannte Referenzpunkte
wahres_minimum = messgröße.min()
wahres_maximum = messgröße.max()

# Zweipunktkalibrierung
a = (wahres_maximum - wahres_minimum) / (messwerte.max() - messwerte.min())
b = wahres_minimum - a * messwerte.min()
korrigierte_messwerte = a * messwerte + b

# Ausgabe
print(f"Wahre Werte:\n{messgröße}")
print(f"Messwerte:\n{messwerte}")
print()
print(f"Zweipunktkalibrierung\n",
      f"a = {a:.1f}\n",
      f"b = {b:.1f}\n",
      f"\na * messwerte + b\n{korrigierte_messwerte}",
      sep = '')
Wahre Werte:
[ 7  8  9 10 11 12 13 14 15 16 17]
Messwerte:
[ 8.66666667  9.33333333 10.         10.66666667 11.33333333 12.
 12.66666667 13.33333333 14.         14.66666667 15.33333333]

Zweipunktkalibrierung
a = 1.5
b = -6.0

a * messwerte + b
[ 7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17.]

Der Empfindlichkeitsfehler kann als \({1 \over a} - 1\) ermittelt werden. Der Nullpunktfehler kann als \(-1 * {b \over a}\) ermittelt werden.

print(f"Empfindlichkeitsfehler: {round( ((1 / a) - 1) * 100, 2)} %")
print(f"Nullpunktfehler: {round(-1 * b / a, 2)}")
Empfindlichkeitsfehler: -33.33 %
Nullpunktfehler: 4.0
Deutsches Institut für Normung. 1995. „Grundlagen der Meßtechnik - Teil 1: Grundbegriffe“. DIN Media. https://doi.org/10.31030/2713411.
———. 2023. „Industrielle Platin-Widerstandsthermometer und Platin-Temperatursensoren (IEC 60751:2022). Deutsche Fassung EN IEC 60751:2022“. DIN Media. https://doi.org/10.31030/3405985.
Grote, Karl-Heinrich, und Jörg Feldhusen, Hrsg. 2011. Dubbel: Taschenbuch für den Maschinenbau. 23. Aufl. Springer Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-17306-6.