bahan <- data.frame(
kode = c("skim", "malto", "lemak", "caco3", "fos", "dha", "premix"),
nama = c("Susu skim bubuk", "Maltodekstrin", "Bubuk lemak nabati (70% lemak)",
"Kalsium karbonat", "FOS-inulin", "Bubuk DHA (mikroenkapsulasi)",
"Premix vitamin-mineral"),
air = c(4, 5, 2, 0, 5, 3, 0),
protein = c(34, 0, 3, 0, 0, 10, 0),
lemak = c(1, 0, 70, 0, 0, 45, 0),
karbo = c(52, 95, 23, 0, 0, 40, 0), # karbohidrat tercerna (laktosa, dll.)
serat = c(0, 0, 0, 0, 95, 0, 0), # serat pangan (FOS-inulin)
Ca = c(1.25, 0, 0.02, 40, 0, 0, 0) # g Ca per 100 g bahan
)
rownames(bahan) <- bahan$kode
bahan1 Formulasi Susu Ibu Hamil: Pendekatan Complete Diet Bertahap
Diadaptasi dari metode formulasi pakan (Chiba, Animal Nutrition Handbook, Section 18)
1.1 Tujuan dan catatan penting
Di materi kuliah, contoh Formulating a Complete Diet memakai kasus untuk pakan babi grower-finisher, dimana protein diseimbangkan terlebih dulu, lalu disusul oleh fosfor, kalsium, dan terakhir protein dikoreksi lagi. Pada perhitungan ini, logika yang sama dipakai untuk merancang susu bubuk ibu hamil.
Hal yang akan dipelajari:
- Menyusun spesifikasi (target zat gizi) dan daftar bahan.
- Menyeimbangkan zat gizi satu per satu dengan persamaan aljabar satu variabel.
- Memahami kenapa perlu koreksi ulang, dan bagaimana iterasi atau sistem persamaan linear menyelesaikannya sekaligus.
- Menghitung nilai gizi lengkap, basis kering, takaran saji, dan premix vitamin-mineral.
Penting: Semua angka komposisi bahan dan target di dokumen ini adalah angka hipotetis untuk latihan, bukan resep produk sungguhan. Formulasi nyata memerlukan data analisis laboratorium setiap lot bahan, uji stabilitas, uji sensori, dan kepatuhan terhadap regulasi BPOM.
Cara pakai: jalankan setiap chunk secara berurutan (tombol ▶ di RStudio), baca penjelasannya, lalu cocokkan hasilnya dengan hitungan manual di kertas. Dokumen ini hanya memakai library base R dan library knitr.
1.2 Langkah 0: Data bahan dan target
1.2.1 Komposisi bahan
Masukkan semua angka dalam gram per 100 g bahan, basis basah, kecuali dinyatakan lain. Dalam contoh kasus ini, susu bubuk ibu hamil yang akan dibuat menggunakan 7 buah bahan sesuai dengan data nama.
Penjelasan singkat tiap bahan:
| Bahan susu ibu hamil | Peran |
|---|---|
| Susu skim bubuk | Sumber protein utama (dan kalsium) |
| Maltodekstrin | Sumber energi dan sebagai bahan “pengisi/penyesuai” |
| Bubuk lemak nabati | Sumber lemak |
| Kalsium karbonat | Sumber kalsium murni |
| FOS-inulin, bubuk DHA, premix | Bahan tetap dengan jumlah yang sudah diputuskan |
Maltodekstrin berperan seperti jagung pada pakan babi: setiap kali bahan lain ditambah, maltodekstrin yang dikurangi supaya total tetap 100 g.
Asumsi yang sama seperti di buku: premix dianggap tidak menyumbang protein, lemak, maupun kalsium (kontribusinya terlalu kecil). Isi premix dirancang terpisah di bagian terakhir.
1.2.2 Target spesifikasi
Target per 100 g bubuk (basis basah):
target <- c(protein = 22, # g/100 g
lemak = 8, # g/100 g
Ca = 1.1) # g/100 g (= 1.100 mg/100 g)
target#> protein lemak Ca
#> 22.0 8.0 1.1
1.2.3 Bahan tetap (fixed ingredients)
tetap <- c(fos = 3, dha = 1, premix = 1) # g per 100 g formula
sisa <- 100 - sum(tetap)
sisa#> [1] 95
Maksudnya: jumlah FOS-inulin, bubuk DHA, dan premix sudah diputuskan di awal (misalnya berdasarkan dosis prebiotik dan target DHA). Jadi hanya 95 g yang tersisa untuk dibagi ke susu skim, maltodekstrin, bubuk lemak, dan kalsium karbonat.
Fungsi untuk menghitung isi gizi sebuah resep
# resep: vektor bernama, satuan g bahan per 100 g formula
hitung_gizi <- function(resep, kolom = c("air", "protein", "lemak", "karbo", "serat", "Ca")) {
komposisi <- as.matrix(bahan[names(resep), kolom])
colSums(komposisi * resep / 100) # gram zat gizi per 100 g formula
}Maksudnya: untuk setiap zat gizi, kontribusi sebuah bahan = jumlah bahan × kadar zat gizi bahan ÷ 100. Semua kontribusi lalu dijumlahkan.
1.3 Langkah 1: Seimbangkan protein
Pertama-tama, hitung kebutuhan susu skim (x) dan maltodekstrin yang akan dipakai untuk susu ibu hamil. Jika 22 gram protein yang dikehendaki, dari DHA dengan kandungan 10% protein dan skim dengan kandungan 34%, maka sisa malto yang dibutuhkan dapat dihitung dengan persamaan ini.
\[\underbrace{0{,}10(1)}_{\text{DHA}} + \underbrace{0{,}34\,x}_{\text{skim}} + \underbrace{0\,(95- x)}_{\text{malto}} = 22\]
protein_tetap <- 0.10 * tetap["dha"] # protein dari bahan tetap
skim <- (target["protein"] - protein_tetap) / 0.34
malto <- sisa - skim
resep1 <- c(skim = unname(skim), malto = unname(malto), tetap)
round(resep1, 3)#> skim malto fos dha premix
#> 64.412 30.588 3.000 1.000 1.000
round(hitung_gizi(resep1), 3)#> air protein lemak karbo serat Ca
#> 4.286 22.000 1.094 62.953 2.850 0.805
Maksudnya: protein diseimbangkan pertama karena susu skim adalah bahan dengan porsi terbesar dan protein adalah zat gizi utama. Maltodekstrin tidak mengandung protein, sehingga persamaannya jadi sangat sederhana.
Hasil check: skim yang dibutuhkan adalah 64.412 g dengan maltodektrin sebanyak 30.588 g untuk mencapai protein 22 g. Meskipun demikian, lemak dan kalsium masih belum mencapai target.
1.3.0.1 Sampingan: cek dengan Pearson Square
kadar_perlu <- (target["protein"] - protein_tetap) / sisa * 100 # % protein campuran
bagian_skim <- abs(kadar_perlu - 0) # selisih dengan maltodekstrin (0%)
bagian_malto <- abs(34 - kadar_perlu) # selisih dengan susu skim (34%)
c(kadar_perlu = unname(kadar_perlu),
skim_g = unname(bagian_skim / (bagian_skim + bagian_malto) * sisa),
malto_g = unname(bagian_malto / (bagian_skim + bagian_malto) * sisa))#> kadar_perlu skim_g malto_g
#> 23.0526 64.4118 30.5882
Hasilnya sama dengan langkah 1. Artinya, aljabar satu variabel dan Pearson Square hanyalah dua cara menulis perhitungan yang sama.
1.4 Langkah 2: Seimbangkan lemak
Bubuk lemak (y) ditambahkan dan maltodekstrin dikurangi sebanyak y.
\[0{,}45(1) + 0{,}01(\text{skim}) + 0{,}70\,y = 8\]
lemak_lain <- 0.45 * tetap["dha"] + 0.01 * resep1["skim"]
lemak_bubuk <- (target["lemak"] - lemak_lain) / 0.70
malto <- resep1["malto"] - lemak_bubuk
resep2 <- c(skim = unname(resep1["skim"]), malto = unname(malto),
lemak = unname(lemak_bubuk), tetap)
round(resep2, 3)#> skim malto lemak fos dha premix
#> 64.412 20.723 9.866 3.000 1.000 1.000
round(hitung_gizi(resep2), 3)#> air protein lemak karbo serat Ca
#> 3.990 22.296 8.000 55.850 2.850 0.807
Maksudnya: susu skim dan bubuk DHA sudah menyumbang sedikit lemak, jadi bubuk lemak hanya perlu menutup kekurangannya. Perhatikan hasil check: lemak sudah tepat 8, tetapi protein naik sedikit di atas 22, karena bubuk lemak juga mengandung 3% protein (dari kaseinat pembawanya). Ini akan dikoreksi di langkah 4.
1.5 Langkah 3: Seimbangkan kalsium
Kalsium karbonat (z) ditambahkan dan maltodekstrin dikurangi lagi.
\[0{,}0125(\text{skim}) + 0{,}0002(\text{bubuk lemak}) + 0{,}40\,z = 1{,}1\]
Ca_lain <- 0.0125 * resep2["skim"] + 0.0002 * resep2["lemak"]
caco3 <- (target["Ca"] - Ca_lain) / 0.40
malto <- resep2["malto"] - caco3
resep3 <- c(skim = unname(resep2["skim"]), malto = unname(malto),
lemak = unname(resep2["lemak"]), caco3 = unname(caco3), tetap)
round(resep3, 3)#> skim malto lemak caco3 fos dha premix
#> 64.412 19.990 9.866 0.732 3.000 1.000 1.000
round(hitung_gizi(resep3), 4)#> air protein lemak karbo serat Ca
#> 3.9533 22.2960 8.0000 55.1542 2.8500 1.1000
Maksudnya: kalsium diseimbangkan terakhir dengan bahan yang hanya mengandung kalsium. Dengan begitu, penambahan CaCO₃ tidak mengganggu protein dan lemak yang sudah pas. Di contoh babi, urutannya fosfor dulu (Dical) baru kalsium (Lime), karena Dical mengandung P dan Ca sehingga ikut menyumbang Ca. Di contoh ini tidak ada bahan yang menyumbang keduanya, jadi cukup satu langkah untuk mineral.
1.6 Langkah 4: Seimbangkan ulang protein
Sekarang jumlah bubuk lemak dan CaCO₃ sudah “dikunci”. Protein dihitung ulang dengan memperhitungkan protein dari bubuk lemak. Ruang untuk susu skim + maltodekstrin menjadi lebih kecil:
ruang <- sisa - resep3["lemak"] - resep3["caco3"]
protein_lain <- 0.10 * tetap["dha"] + 0.03 * resep3["lemak"]
skim <- (target["protein"] - protein_lain) / 0.34
malto <- ruang - skim
resep4 <- c(skim = unname(skim), malto = unname(malto),
lemak = unname(resep3["lemak"]), caco3 = unname(resep3["caco3"]), tetap)
round(resep4, 3)#> skim malto lemak caco3 fos dha premix
#> 63.541 20.861 9.866 0.732 3.000 1.000 1.000
1.7 Cek akhir: kenapa lemak dan kalsium sedikit meleset?
cek <- function(resep) {
hasil <- hitung_gizi(resep)[names(target)]
data.frame(zat_gizi = names(target), target = unname(target),
hasil = round(unname(hasil), 4),
selisih = round(unname(hasil - target), 4))
}
cek(resep4)Di langkah 4, susu skim berkurang sedikit. Susu skim juga membawa lemak dan kalsium, jadi lemak dan kalsium ikut turun sedikit di bawah target.
Ini keterbatasan metode bertahap: setiap koreksi mengganggu zat gizi lain yang sudah pas. Di buku Chiba, selisih kecil seperti ini biasanya dibiarkan karena masih dalam batas toleransi. Ada dua cara untuk mendapatkan jawaban yang tepat: mengulang langkah 1–4 sampai stabil (iterasi), atau menyelesaikan semuanya sekaligus sebagai sistem persamaan.
1.7.1 Cara A: iterasi (ulangi langkah sampai stabil)
iterasi <- function(maks = 50, toleransi = 1e-8) {
lemak_b <- 0; caco3_b <- 0
for (i in 1:maks) {
# Langkah protein
skim <- (target["protein"] - 0.10 * tetap["dha"] - 0.03 * lemak_b) / 0.34
# Langkah lemak
lemak_baru <- (target["lemak"] - 0.45 * tetap["dha"] - 0.01 * skim) / 0.70
# Langkah kalsium
caco3_baru <- (target["Ca"] - 0.0125 * skim - 0.0002 * lemak_baru) / 0.40
berubah <- abs(lemak_baru - lemak_b) + abs(caco3_baru - caco3_b)
lemak_b <- lemak_baru; caco3_b <- caco3_baru
if (berubah < toleransi) break
}
malto <- sisa - skim - lemak_b - caco3_b
list(putaran = i,
resep = c(skim = unname(skim), malto = unname(malto),
lemak = unname(lemak_b), caco3 = unname(caco3_b), tetap))
}
hasil_iterasi <- iterasi()
hasil_iterasi$putaran#> [1] 5
round(hasil_iterasi$resep, 4)#> skim malto lemak caco3 fos dha premix
#> 63.5402 20.8224 9.8780 0.7594 3.0000 1.0000 1.0000
cek(hasil_iterasi$resep)Maksudnya: langkah protein → lemak → kalsium diulang terus. Karena pengaruh silangnya kecil, hasilnya cepat stabil dalam beberapa putaran saja, dan semua target tercapai tepat.
1.7.2 Cara B: selesaikan sekaligus sebagai sistem persamaan linear
Ada 4 bahan yang belum diketahui (skim, malto, lemak, CaCO₃) dan 4 syarat:
- Total keempatnya = 95 g
- Protein total = 22 g
- Lemak total = 8 g
- Kalsium total = 1,1 g
Empat persamaan dengan empat variabel bisa diselesaikan langsung dengan aljabar matriks (A · x = b):
var_bebas <- c("skim", "malto", "lemak", "caco3")
zat <- c("protein", "lemak", "Ca")
A <- rbind(total = rep(1, length(var_bebas)),
t(as.matrix(bahan[var_bebas, zat])) / 100)
colnames(A) <- var_bebas
kontribusi_tetap <- hitung_gizi(tetap, kolom = zat)
b <- c(total = sisa, target[zat] - kontribusi_tetap)
A#> skim malto lemak caco3
#> total 1.0000 1 1e+00 1.0
#> protein 0.3400 0 3e-02 0.0
#> lemak 0.0100 0 7e-01 0.0
#> Ca 0.0125 0 2e-04 0.4
b#> total protein lemak Ca
#> 95.00 21.90 7.55 1.10
solusi <- solve(A, b)
resep_final <- c(solusi, tetap)
round(resep_final, 4)#> skim malto lemak caco3 fos dha premix
#> 63.5402 20.8224 9.8780 0.7594 3.0000 1.0000 1.0000
cek(resep_final)Maksudnya: baris pertama matriks A adalah syarat total berat (koefisien 1 untuk setiap bahan). Baris berikutnya adalah kadar protein, lemak, dan Ca setiap bahan (dalam desimal). Vektor b adalah target dikurangi sumbangan bahan tetap. Hasilnya sama dengan cara iterasi. Metode bertahap di slide sebenarnya adalah cara manual untuk menyelesaikan sistem persamaan ini. Software formulasi (seperti Excel Solver) dapat mengerjakan hal yang sama, dan biasanya ditambah tujuan untuk meminimalkan harga (linear programming). Lakukan dengan Excel Solver pada file formulasi_susu_ibu_hamil.xlsx dan bandingkan hasilnya.
1.8 Resep akhir dan nilai gizi lengkap
resep_tabel <- data.frame(
bahan = bahan[names(resep_final), "nama"],
g_per_100g = round(unname(resep_final), 2)
)
resep_tabelsum(resep_final) # harus 100#> [1] 100
gizi <- hitung_gizi(resep_final)
energi <- 4 * gizi["protein"] + 9 * gizi["lemak"] + 4 * gizi["karbo"] + 2 * gizi["serat"]
data.frame(
komponen = c("Air (g)", "Protein (g)", "Lemak (g)", "Karbohidrat (g)",
"Serat FOS-inulin (g)", "Kalsium (mg)", "Energi (kkal)"),
per_100g = round(c(gizi[c("air", "protein", "lemak", "karbo", "serat")],
gizi["Ca"] * 1000, energi), 1)
)Maksudnya: energi dihitung dengan faktor Atwater (protein dan karbohidrat 4 kkal/g, lemak 9 kkal/g). FOS-inulin dihitung 2 kkal/g karena hanya sebagian energinya diperoleh lewat fermentasi di usus besar.
1.8.1 Konversi ke basis kering
BK <- 100 - gizi["air"] # % bahan kering formula
gizi_bk <- gizi[c("protein", "lemak", "Ca")] / (BK / 100)
c(bahan_kering_persen = unname(BK))#> bahan_kering_persen
#> 96.0397
round(rbind(basis_basah = gizi[c("protein", "lemak", "Ca")],
basis_kering = gizi_bk), 3)#> protein lemak Ca
#> basis_basah 22.000 8.00 1.100
#> basis_kering 22.907 8.33 1.145
Maksudnya: susu bubuk sangat kering (bahan kering ±96%), jadi basis basah dan basis kering hanya sedikit berbeda. Inilah alasan asumsi 90% bahan kering dari formulasi pakan tidak cocok untuk susu bubuk: kadar air sebaiknya diukur langsung. Sesuai rule of thumb, dari basis basah ke basis kering kadar zat gizi naik.
1.9 Takaran saji
Misalkan satu takaran saji adalah 45 g bubuk (dilarutkan dalam ±200 mL air), diminum 2 kali sehari.
saji_g <- 45
per_saji <- c(gizi[c("protein", "lemak", "karbo")] * saji_g / 100,
Ca_mg = unname(gizi["Ca"] * 1000 * saji_g / 100),
energi_kkal = unname(energi * saji_g / 100))
round(per_saji, 1)#> protein lemak karbo Ca_mg energi_kkal
#> 9.9 3.6 25.0 495.0 174.5
round(per_saji * 2, 1) # per hari (2 saji)#> protein lemak karbo Ca_mg energi_kkal
#> 19.8 7.2 49.9 990.0 348.9
Maksudnya: angka ini dibandingkan dengan Angka Kecukupan Gizi (AKG) ibu hamil untuk melihat berapa persen kebutuhan yang dipenuhi formula. Gunakan tabel AKG terbaru (Permenkes No. 28 Tahun 2019) sesuai trimester. Formula ibu hamil dirancang sebagai pelengkap, bukan untuk memenuhi 100% kebutuhan.
1.10 Merancang premix vitamin-mineral
Premix sudah “dipesan” 1 g per 100 g formula. Sekarang isinya dirancang, mengikuti bagian Formulating a TM or Vit premix di buku Chiba: tentukan kebutuhan, perhitungkan kemurnian sumber, lalu sisanya diisi bahan pembawa (carrier).
Target per 100 g formula (hipotetis):
premix_g <- tetap["premix"] # 1 g premix per 100 g formula
premix <- data.frame(
zat = c("Asam folat", "Besi (Fe)", "Seng (Zn)", "Yodium (I)", "Vitamin D3"),
target = c(400, 12, 6, 90, 400),
satuan = c("mcg", "mg", "mg", "mcg", "IU"),
sumber = c("Asam folat", "Fero fumarat", "Seng sulfat monohidrat",
"Kalium iodida", "Vitamin D3 bubuk 100.000 IU/g"),
# kadar zat dalam bahan sumber (fraksi berat, atau IU per g untuk vitamin D)
kadar = c(1.00, 0.3287, 0.3643, 0.7645, 100000),
overage = c(0.20, 0, 0, 0, 0.20) # kelebihan untuk mengimbangi kehilangan selama penyimpanan
)
# ubah target ke gram zat (vitamin D tetap dalam IU)
ke_gram <- ifelse(premix$satuan == "mcg", 1e-6, ifelse(premix$satuan == "mg", 1e-3, 1))
premix$target_x_overage <- premix$target * (1 + premix$overage)
premix$g_sumber <- premix$target_x_overage * ke_gram / premix$kadar
carrier <- premix_g - sum(premix$g_sumber)
premix$persen_dalam_premix <- premix$g_sumber / premix_g * 100
knitr::kable(premix[, c("zat", "target", "satuan", "sumber", "overage",
"g_sumber", "persen_dalam_premix")], digits = 4)| zat | target | satuan | sumber | overage | g_sumber | persen_dalam_premix |
|---|---|---|---|---|---|---|
| Asam folat | 400 | mcg | Asam folat | 0.2 | 0.0005 | 0.0480 |
| Besi (Fe) | 12 | mg | Fero fumarat | 0.0 | 0.0365 | 3.6507 |
| Seng (Zn) | 6 | mg | Seng sulfat monohidrat | 0.0 | 0.0165 | 1.6470 |
| Yodium (I) | 90 | mcg | Kalium iodida | 0.0 | 0.0001 | 0.0118 |
| Vitamin D3 | 400 | IU | Vitamin D3 bubuk 100.000 IU/g | 0.2 | 0.0048 | 0.4800 |
c(carrier_maltodekstrin_g = unname(carrier),
carrier_persen = unname(carrier / premix_g * 100))#> carrier_maltodekstrin_g carrier_persen
#> 0.941625 94.162488
Maksudnya:
- Kemurnian sumber. Kita tidak menambahkan “besi murni”, melainkan senyawa seperti fero fumarat, yang hanya mengandung ±32,9% Fe. Jadi bahan sumber yang dibutuhkan = kebutuhan zat ÷ kadar zat dalam sumber.
- Overage. Vitamin seperti asam folat dan vitamin D bisa rusak selama pengolahan dan penyimpanan, sehingga industri biasanya menambahkan lebih (di sini dimisalkan 20%) agar kadar di akhir masa simpan tetap memenuhi label. Mineral stabil, jadi tidak perlu overage.
- Carrier. Semua bahan aktif hanya berbobot kecil, jadi sisanya diisi maltodekstrin supaya premix bisa ditimbang dan dicampur merata. Ini sama dengan poin “use a carrier” di buku.
- Vitamin A sengaja tidak dimasukkan dalam bentuk retinol, karena asupan retinol berlebih saat hamil berisiko teratogenik. Kalau ingin ditambahkan, lebih aman dalam bentuk beta-karoten.
1.11 Latihan untuk mahasiswa
- Ubah target protein menjadi 25 g/100 g. Jalankan ulang semua chunk. Bahan apa yang naik dan apa yang turun? Kenapa?
- Ganti susu skim dengan isolat protein kedelai (misalnya protein 88%, lemak 1%, Ca 0,2%). Apa yang terjadi pada kebutuhan CaCO₃? Jelaskan.
- Hitung ulang langkah 1 secara manual dengan Pearson Square, lalu bandingkan dengan hasil R.
- Tambahkan syarat baru: karbohidrat total tidak boleh lebih dari 55 g/100 g. Apakah masih bisa diselesaikan dengan 4 bahan yang ada? Bahan apa yang perlu ditambahkan?
- Tambahkan kolom harga per kg ke data bahan, lalu hitung biaya bahan per kg formula. Diskusikan: bagaimana jika ingin formula termurah yang tetap memenuhi semua target?
- Jalankan formula yang lebih kompleks dengan 13 bahan baku dengan 12 komponen yang diatur pada file
formulasi_susu_ibu_hamil_kompleks.xlsx
1.12 Keterbatasan pendekatan ini
- Pendekatan ini hanya menjamin angka gizi di atas kertas. Formula sungguhan juga harus mempertimbangkan kelarutan, rasa, aroma, stabilitas oksidatif (terutama DHA), dan interaksi antar-zat (misalnya kalsium menghambat penyerapan besi).
- Komposisi bahan harus berasal dari analisis laboratorium tiap lot, bukan angka tabel.
- Batas atas zat gizi (misalnya retinol dan yodium) sama pentingnya dengan batas bawah.
- Produk akhir harus memenuhi regulasi BPOM untuk pangan olahan keperluan gizi khusus, termasuk batas cemaran dan persyaratan label.
1.13 Rujukan
Chiba, L. I. (2009). Animal Nutrition Handbook, Section 18: Diet Formulation & Feed Ingredients (hlm. 481–491). Auburn University.