Multi-variate regression in TensorFlow.js - tensorflow.js

I'm following the tutorials for TensorFlow.js: I've followed along their basic regression tutorial and it worked perfectly fine.
Then I tried to expand on that to perform a multi-variate prediction, but I can't understand what I'm doing wrong. I loaded my structured data, created the model specifying the input shape, then I changed the tensor conversion to obtain an array of arrays with the correct shape. When I pass the inputs to the model.fit() function, I get the following error:
Error when checking model input: the Array of Tensors that you are passing to your model is not the size the model expected. Expected to see 1 Tensor(s), but instead got the following list of Tensor(s): 0.3263888888888889,0.42857142857142855,0.22580645161290322,0.75,1,0.2,0.7583333333333333,0.42857142857142855,0.22580645161290322,0.75,0,0.4,0.3236111111111111,0.14285714285714285,0.3870967741935484,0.75,1,0.2,0.7590277777777777,0.7142857142857143,0.2903225806451613,0.75,0,0.2,0.7763888888888889,0.2857142857142857,0.1935483870967742,0.75,0,0.4,0.32430555555555557,0.5714285714285714,0.25806451612903225,0.75,1,0.2,0.325,0.7142857142857143,0.2903225806451613,0.75,1,0.2,0.32569444444444445,0.14285714285714285,0.16129032258064516,0.75,1,0.2,0.7687499999999999,0.14285714285714285,0.16129032258064516,0.75,0,0.4,0.32916666666666666,0.2857142857142857,0.1935483870967742,0.75,1,0.4
Here is my code:
async function getData() {
return [
{"direction":1,"dow":1,"month":9,"dom":5,"start":749,"arrival":832,"variant1":1,"startHour":7.816666666666666,"arrivalHour":8.533333333333333,"timeTaken":0.7166666666666668},
{"direction":0,"dow":1,"month":9,"dom":5,"start":1827,"arrival":1917,"variant1":2,"startHour":18.45,"arrivalHour":19.283333333333335,"timeTaken":0.8333333333333357},
{"direction":1,"dow":2,"month":9,"dom":6,"start":754,"arrival":846,"variant1":2,"startHour":7.9,"arrivalHour":8.766666666666667,"timeTaken":0.8666666666666671},
{"direction":0,"dow":2,"month":9,"dom":6,"start":1838,"arrival":1934,"variant1":2,"startHour":18.633333333333333,"arrivalHour":19.566666666666666,"timeTaken":0.9333333333333336},
{"direction":1,"dow":3,"month":9,"dom":7,"start":750,"arrival":836,"variant1":1,"startHour":7.833333333333333,"arrivalHour":8.6,"timeTaken":0.7666666666666666},
{"direction":0,"dow":3,"month":9,"dom":7,"start":1812,"arrival":1855,"variant1":2,"startHour":18.2,"arrivalHour":18.916666666666668,"timeTaken":0.7166666666666686},
{"direction":1,"dow":4,"month":9,"dom":8,"start":747,"arrival":834,"variant1":1,"startHour":7.783333333333333,"arrivalHour":8.566666666666666,"timeTaken":0.7833333333333332},
{"direction":1,"dow":5,"month":9,"dom":9,"start":748,"arrival":834,"variant1":1,"startHour":7.8,"arrivalHour":8.566666666666666,"timeTaken":0.7666666666666666},
{"direction":0,"dow":5,"month":9,"dom":9,"start":1813,"arrival":1855,"variant1":1,"startHour":18.216666666666665,"arrivalHour":18.916666666666668,"timeTaken":0.7000000000000028},
{"direction":1,"dow":1,"month":9,"dom":12,"start":746,"arrival":833,"variant1":1,"startHour":7.766666666666667,"arrivalHour":8.55,"timeTaken":0.7833333333333341}
]
}
async function run() {
// Load the original input data that we are going to train on.
const data = await getData();
// Create the model
const model = createModel();
tfvis.show.modelSummary({name: 'Model Summary'}, model);
// Convert the data to a form we can use for training.
const tensorData = convertToTensor(data);
const {inputs, labels} = tensorData;
// Train the model
console.log('Starting Training');
await trainModel(model, inputs, labels);
console.log('Done Training');
}
document.addEventListener('DOMContentLoaded', run);
function createModel() {
const model = tf.sequential()
model.add(tf.layers.dense({inputShape: [ 6 ], units: 10}))
model.add(tf.layers.dense({units: 5}))
return model
}
function convertToTensor(data) {
return tf.tidy(() => {
tf.util.shuffle(data)
// Ad-hoc normalization. Variant1 is one of a few values (1, 2, 3...): dividing by 5 should never give much more than 1 in the future
const inputs = data.map(d => [
d.startHour / 24,
d.dow / 7,
d.dom / 31,
d.month / 12,
d.direction,
d.variant1 / 5,
])
const labels = data.map(d => d.timeTaken)
const inputTensor = tf.tensor2d(inputs, [ inputs.length, 6 ])
const labelTensor = tf.tensor2d(labels, [ labels.length, 1 ])
return {
inputs,
labels,
}
});
}
async function trainModel(model, inputs, labels) {
model.compile({
optimizer: tf.train.adam(),
loss: tf.losses.meanSquaredError,
metrics: ['mse'],
})
const batchSize = 2
const epochs = 100
return await model.fit(inputs, labels, {
batchSize,
epochs,
shuffle: true,
callbacks: tfvis.show.fitCallbacks(
{ name: 'Training Performance' },
['loss', 'mse'],
{ height: 200, callbacks: ['onEpochEnd'] }
)
})
}
<!DOCTYPE html>
<html>
<head>
<title>TensorFlow.js Tutorial</title>
<!-- Import TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#2.0.0/dist/tf.min.js"></script>
<!-- Import tfjs-vis -->
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs-vis#1.0.2/dist/tfjs-vis.umd.min.js"></script>
</head>
<body>
<!-- Import the main script file -->
<script type="module" src="tfjs-regression-commute-times.so.js"></script>
</body>
</html>
I have a decent grasp of ML concepts, but it's the first time I try to actually implement them, so please point out any misconception I may have about layers or models or whatnot.

Related

What is the ideal way to use subscription with react apollo GraphQL

I am working on a react project which uses GraphQl for API integration, which I did perfectly.
Now I am working on a module where I need to use subscription.
What I am doing
I have charts to show one UI, which I have created successfully using ReCharts.
So initially I a getting data from query and displaying it on UI which works totally fine.
Than as per requirement There is some live data which I am getting through subscription i.e web socket
So what I am doing is I stored query data in a state and when I got the live data I am appending the live data to that state and UI is updating correctly.
Issue I am facing
As I have many charts on UI so I am conditionally checking for which chart I am getting data and than appending to the particular one.
But what happens is when I got a lot of data which is coming every 2-6 seconds it hangs the browser after sometime, like after 10 minutes or 15 minutes, which is bit frustrating.
I don't know What is wrong in this, may be my approach is not good or something else
My code what I did
// below query is for getting list of factoories
//const { loading: loadingFactoryy, data: factoryList } = useQuery( FACTORYLIST);
// from above query I ll get data like below state
// as I am not connected to backend so I am taking data statically
const [factoryList, setFactoryList] = useState([
{
factoryId: 16,
factoryName: "factory one",
__typename: "user_factoryNames"
},
{
factoryId: 17,
factoryName: "factory two",
__typename: "user_factoryNames"
},
{
factoryId: 18,
factoryName: "factory Three",
__typename: "user_factoryNames"
},
{
factoryId: 19,
factoryName: "factory four",
__typename: "user_factoryNames"
}
]);
My Machine code
// below query to get the machines for each factories, if i click on
//factoryone it will give me all machines of factory one
// const [
// getMachines,
// { loading: loadingMachines, data: machineData },
// ] = useLazyQuery(FACTORYMACHINEBYID);
//I am using lazyquery so when I click I ll get the data
// useEffect(() => { this will run when page loades first time
// if (factoryList !== undefined) {
// if (factoryList.getUserFactorydNames.length > 0) {
// getInitialDataFun({
// variables: {
// factoryId:
// factoryList?.getUserFactorydNames[parseInt(activeDashboard)]
// ?.factoryId,
// },
// });
// }
// }
// }, [active_tab, factoryId, getInitialDataFun]);
//all functions for factories click
let active_tab = localStorage.getItem("active_tab") || 0;
const [active_menu, setActive_menu] = useState(Number(active_tab));
const [machineList, setmachineList] = useState([
{
chartId: 12,
chartBame: "machine1",
data: [
{
dValue: 5,
dName: "Data1"
},
{
dValue: 10,
dName: "Data2"
}
]
},
{
chartId: 13,
chartBame: "machine2",
data: [
{
dValue: 5,
dName: "Data1"
},
{
dValue: 10,
dName: "Data2"
}
]
}
]);
My subscription code
// my subscription code
// const {
// data: LiveDataMachine,
// error: errorLiveDataMachine,
// } = useSubscription(LIVEDATAMACHINEFUN, {
// variables: { topic: { topic: 'livemessage/' } },
// });
const factoryClick = (li, ind) => {
setActive_menu(ind);
localStorage.setItem("active_tab", ind);
};
//My live data looks like below
// {
// "chartId": 16,
// "chartBame": "machine1",
// "data": [
// {
// "dValue": 7,
// "dName": "Data1"
// },
// {
// "dValue": 18,
// "dName": "Data2"
// }
// ]
// }
// So what I am doing is whenever I am getting the live data
//I am looping it with main machine data and checking for chartId
//once it matches I am appending the data like below
// useEffect(() => {
// if(machineList){
// machineData.map((li,ind)=>{
// if(LiveDataMachine.chartId===li.chartId){
// setmachineList(machineList=>[...machineList,LiveDataMachine])
// }
// })
// }
// }, [LiveDataMachine]);
// but what is hapenning is it is updating the UI so fast that my browser is getting hanged
return (
<div className="App">
<FactoryComp
factoryData={factoryList}
active_menu={active_menu}
factoryClick={factoryClick}
/>
<hr />
<MachinesComp machineData={machineList} />
</div>
above is how I am writing my code and managing GraphQl subscription, I don't know what I am doing wrong which is making my browser hangs alot
here I am looking for a correct approach by which I can use subscription with my query.
Here is my working code I have written everything as a comment for better uderstanding
Looks like you are calling setmachineList in the if inside a render. It will endlessly cause component updates, thereby cycling it. Perhaps it is better to move this logic to useEffect?
useEffect(() => {
if(machineList){
machineData.forEach((li,ind)=>{
if(LiveDataMachine.chartId===li.chartId){
setmachineList(machineList=>[...machineList,LiveDataMachine])
}
})
}
}, [LiveDataMachine]);

ReactNativeFirebase issues with BATCH querying of arrays

i thank you all for being there for all who have issues with codes.. i have an issue too, and at this point, i've ran out of ideas, i've read through blogs and followed gists i possibly can, regarding this, but none worked for me...
THE PROBLEM:
i'm working on pagination in my app, which was working fine earlier, until i added a feature (ability to let users add posts they don't want to see to thier blacklist fields, these posts won't be shown to them). Now, they've blacklisted more than 10 ids, (let's say 15 ids).
due to firestore query limitations while using the (not-in, in, etc), if the array you are filtering from is more than 10 values, you'll have to run a batch request, probably in a loop, which goes with 10 values in an array at a time, after u must have split the arrays in chunks.
Now, the thing is, on the first iteration, i'm successfully getting my desired posts, also i'm storing the lastQuerySnapshot so i can use it on the second iteration, THIS IS WHERE THE ISSUE IS, for some reasons, i still get the posts whose ids are contained in the 2nd iteration, i've logged as many as possible, but couldn't trace successfully, i noticed probably this might be an issue with startAfter(doc) from Rnfirebase, but i'm not sure.
i'll be really happy if you help me on this, i've been on this for weeks now. here's my codes
export const getFilteredPosts = async (
postsBlacklisted,
lastItem,
limit = 10,
) => {
try {
// const baseUrl = firestore().collectionGroup('AllPosts').orderBy('postedOn');
// const baseUrl = firestore().collectionGroup('AllPosts').orderBy('postID');
const baseUrl = firestore()
.collection('AllPosts')
.orderBy('postID', 'desc');
const postCondition = lastItem ? baseUrl.startAfter(lastItem) : baseUrl;
if (!postsBlacklisted.length > 0) {
console.log('FETCHING WITHOUT FILTER, NO BLACKLISTS ', postsBlacklisted);
//just get all random post.
let response = await postCondition.limit(limit).get();
if (response) {
const lastVisibleItem = response.docs[response.docs.length - 1];
let results = response.docs.map(result => ({
id: result.id,
...result.data(),
}));
return {postsArray: results, lastVisibleItem: lastVisibleItem};
} else {
console.log('check ur filterOutBlackList method');
}
} else {
console.log(' posts black list passed!!!', postsBlacklisted);
}
if (postsBlacklisted || postsBlacklisted.length > 0) {
console.log('FETCHING with FILTER, see BLACKLISTS ', postsBlacklisted);
var copiedPostsBlacklisted = [...postsBlacklisted];
var batches = [];
var index = 0;
var batchChunks = [...splitArrayInChunks(copiedPostsBlacklisted, 10)];
var lastQueryResult;
while (index < batchChunks.length) {
if (lastQueryResult) {
const results = await postCondition
.where('postID', 'not-in', batchChunks[index])
.startAfter(lastQueryResult)
.limit(10)
.get();
if (results) {
batches.push(results.docs);
}
} else {
const results = await postCondition
.where('postID', 'not-in', batchChunks[index])
.limit(10)
.get();
if (results) {
lastQueryResult = results.docs[results.docs.length - 1];
batches.push(results.docs);
}
}
index++;
}
var allPosts = batches.flat().map(result => ({
id: result.id,
...result.data(),
}));
return {
lastVisibleItem: lastQueryResult,
postsArray: allPosts,
};
}
} catch (error) {
console.log('FATAL ERROR ', error.message);
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
**the function that splits the array is : **
//this function returns a splitted array in chunks of 10, if the array passed has more than 10 values
export function* splitArrayInChunks(arr, n) {
for (let i = 0; i < arr.length; i += n) {
yield arr.slice(i, i + n);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
**the way i'm calling this getFilteredPosts **
const querySnapshot = await getFilteredPosts(
blackListedPosts,
afterDoc,
limit,
);
//initially, the "afterDoc" is null, becuz it's the first time i'm calling this function, i've disabled the load more functionality to enable me focus on debugging where the blacklist wierd issue is comming from. the "blackListedPosts" is soomething like this = [i hold more than 10 posts ids], the "limit" is exactly 10
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
here's my package.json, a few dependences
"#react-native-firebase/app": "^14.7.0",
"#react-native-firebase/auth": "^14.7.0",
"#react-native-firebase/firestore": "^14.7.0",
"#react-native-firebase/storage": "^14.7.0",
"react": "17.0.2",
"react-native": "0.67.3",

How to predict after training data is normalized?

I am learning TensorFlow.js using the official documentation and modified the code mentioned in Codelab to output pounds when given a kg value as input.
So I have a run function which runs when DOM is loaded.
async function run() {
const model = createModel();
const data = createData();
const tensorData = convertToTensor(data);
const { inputs, labels } = tensorData;
// Train Model
await trainModel(model, inputs, labels);
console.log('Training Complete');
// Predict
const normalizedPredictData = normalizeData([5]);
const { normalizedPredictDataVal, predictValMax, predictValMin } = normalizedPredictData;
const output = model.predict(normalizedPredictDataVal);
const finalOutput = unNormalizeData(output, predictValMax, predictValMin);
console.log(finalOutput.print());
}
document.addEventListener('DOMContentLoaded', run);
createModel creates a simple sequential model with 2 layers - one hidden layer and one output layer.
function createModel() {
const model = tf.sequential();
// Hidden Layer
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
// Output Layer
model.add(tf.layers.dense({ units: 1 }));
return model;
}
createData is a function which generates 500 values for training.
function createData() {
const data = {
inputs: Array.from({ length: 500 }, (x, i) => i),
labels: Array.from({ length: 500 }, (x, i) => i * 2.2)
};
return data;
}
Inputs run from 0 to 499 and labels are just input * 2.2 because I want to predict pounds when a kg value is given as input.
convertToTensor function normalizes the generated data after converting it to tensors.
function convertToTensor(data) {
return tf.tidy(() => {
tf.util.shuffle(data);
const inputs = data.inputs;
const labels = data.labels;
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);
// Normalize Data
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = inputTensor.max();
const labelMin = inputTensor.min();
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
return {
inputs: normalizedInputs,
labels: normalizedLabels,
inputMax,
inputMin,
labelMax,
labelMin
};
});
}
Finally the data is trained using trainModel
async function trainModel(model, inputs, labels) {
model.compile({
optimizer: tf.train.adam(),
loss: tf.losses.meanSquaredError,
metrics: ['mse']
});
const batchSize = 32;
const epochs = 50;
return await model.fit(inputs, labels, {
batchSize,
epochs,
shuffle: true,
callbacks: tfvis.show.fitCallbacks(
{ name: 'Training Performance' },
['loss', 'mse'],
{ height: 200, callbacks: ['onEpochEnd'] }
)
});
}
Now that the data is trained, it's time to predict values. As the model is trained with normalized values, I am only passing normalized input values to predict function.
function normalizeData(value) {
const predictValTensor = tf.tensor2d(value, [value.length, 1]);
const predictValMax = predictValTensor.max();
const predictValMin = predictValTensor.min();
const normalizedPredictDataVal = predictValTensor.sub(predictValMin).div(predictValMax.sub(predictValMin));
return {
normalizedPredictDataVal,
predictValMax,
predictValMin
};
}
The above function converts the value to tensor, normalizes it and returns the result which is then passed to predict function for an output value. As the input was normalized, the output needs to be unNormalized so have created a function to unnormalize it.
function unNormalizeData(value, predictMax, predictMin) {
const unNormPredictVal = value.mul(predictMax.sub(predictMin)).add(predictMin);
return unNormPredictVal;
}
Once the output is unNormalized, I am simply logging it to console. But it is only outputting the value which I had given as input. In this case, the value is 5.
Code till training data is working fine. I think the error lies where I am trying to normalize and unnormalize the value for predict.
The predicted value should be normalized by using the max and min value of the training sample.
There should not be predictValMax (respectively predictValMin) different from inputMax (respectively inputMin)
const predictValMax = predictValTensor.max();
const predictValMin = predictValTensor.min();
A prediction of a feature should be invariant by dataset of features.
Training set of feature
[-5, 5], inputMin = -5, inputMax = 5; normalized = [0, 0.5]
Given this two test sets of features:
[5, 6], predictMin = 5, predictMax = 6; normalized = [0, 1];
[5], predictMin = 5, predictMax = 6; normalize = [1] // ( though a division by zero occurs here).
The normalized value for 5 is different in the test sets. It is also different from the normalized value in the training data. The model will end up predicting different value for the same feature 5 each time that it occurs because its normalized value is dataset dependant.
This would not happen if the same normalized parameters (inputMin, inputMax) are applied for each predicted value.
function createModel() {
const model = tf.sequential();
// Hidden Layer
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
// Output Layer
model.add(tf.layers.dense({ units: 1 }));
return model;
}
let inputMin, inputMax, labelMin, labelMax
function createData() {
const data = {
inputs: Array.from({ length: 500 }, (x, i) => i),
labels: Array.from({ length: 500 }, (x, i) => i * 2.2)
};
return data;
}
function convertToTensor(data) {
return tf.tidy(() => {
tf.util.shuffle(data);
const inputs = data.inputs;
const labels = data.labels;
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);
inputTensor.print();
labelTensor.print()
// Normalize Data
inputMax = inputTensor.max();
inputMin = inputTensor.min();
labelMax = inputTensor.max();
labelMin = inputTensor.min();
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
return {
inputs: normalizedInputs,
labels: normalizedLabels,
inputMax,
inputMin,
labelMax,
labelMin
};
});
}
async function trainModel(model, inputs, labels) {
const learningRate = 0.01;
const optimizer = tf.train.sgd(learningRate);
// tf.train.adam()
model.compile({
optimizer: optimizer ,
loss: tf.losses.meanSquaredError,
metrics: ['mse']
});
const batchSize = 32;
const epochs = 200;
inputs.print()
labels.print()
return await model.fit(inputs, labels, {
batchSize,
epochs,
shuffle: true,
callbacks: tfvis.show.fitCallbacks(
{ name: 'Training Performance' },
['loss', 'mse'],
{ height: 200, callbacks: ['onEpochEnd'] }
)
});
}
function normalizeData(value) {
const predictValTensor = tf.tensor2d(value, [value.length, 1]);
//const predictValMax = predictValTensor.max();
//const predictValMin = predictValTensor.min();
const normalizedPredictDataVal = predictValTensor.sub(inputMin).div(inputMax.sub(inputMin));
return {
normalizedPredictDataVal,
inputMax,
inputMin
};
}
function unNormalizeData(value, predictMax, predictMin) {
const unNormPredictVal = value.mul(inputMax.sub(inputMin)).add(inputMin);
return unNormPredictVal;
}
async function run() {
const model = createModel();
const data = createData();
const tensorData = convertToTensor(data);
const { inputs, labels } = tensorData;
await trainModel(model, inputs, labels);
console.log('Training Complete');
const normalizedPredictData = await normalizeData([1000, 6, 7]);
console.log('normalizedinput')
normalizedPredictData.normalizedPredictDataVal.print()
const { normalizedPredictDataVal, predictValMax, predictValMin } = normalizedPredictData;
const output = await model.predict(normalizedPredictDataVal);
console.log(output.print());
const finalOutput = await unNormalizeData(output, predictValMax, predictValMin);
console.log(finalOutput.print());
}
document.addEventListener('DOMContentLoaded', run);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#1.0.0/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs-vis#1.0.2/dist/tfjs-vis.umd.min.js"></script>
<script src="index.js"></script>
</body>
</html>

tfjs codelab 'making predictions from 2D data' use of values keyword in tf-vis.render.scatterplot

Why can't I replace values with another variable name like values1?
values is a js keyword, so I think there is something really fancy going on here.
In this codelab:
https://codelabs.developers.google.com/codelabs/tfjs-training-regression/index.html#2
In this code snippet:
async function run() {
// Load and plot the original input data that we are going to train on.
const data = await getData();
const values = data.map(d => ({
x: d.horsepower,
y: d.mpg,
}));
tfvis.render.scatterplot(
{name: 'Horsepower v MPG'},
{values},
{
xLabel: 'Horsepower',
yLabel: 'MPG',
height: 300
}
);
// More code will be added below
}
The tfjs documentation doesn't require the use of the values keyword:
https://js.tensorflow.org/api_vis/latest/#render.scatterplot
If I set up the HTML as instructed in the tutorial and use the same js code, I get the expected plot in my browser (firefox).
If I use the following js code, it breaks. Browser remains completely blank, console error message states: TypeError: r is undefined
In the code below, values in both places is changed to values1. That's the only change.
async function getData() {
const dataRequest = await fetch('https://storage.googleapis.com/tfjs-tutorials/carsData.json');
const data = await dataRequest.json();
const cleaned = data.map(car => ({
mpg: car.Miles_per_Gallon,
hp: car.Horsepower,
}))
.filter(car => (car.mpg != null && car.hp != null));
return cleaned
}
async function run() {
// Load and plot the original input data that we are going to train on.
const data = await getData();
const values1 = data.map(d => ({
x: d.hp,
y: d.mpg,
}));
tfvis.render.scatterplot(
{name: 'Horsepower v MPG'},
{values1},
{
xLabel: 'Horsepower',
yLabel: 'MPG',
height: 300
}
);
// More code will be added below
}
document.addEventListener('DOMContentLoaded', run);
I'd expect the above code to result in a plot as with the original code, but I get no plot and a TypeError instead.
values is not a js keyword. It is a property of the object data that is a parameter of scatterplot. If you want to change the const values to values1, you have to do the following:
tfvis.render.scatterplot(
{name: 'Horsepower v MPG'},
{values: values1},
{
xLabel: 'Horsepower',
yLabel: 'MPG',
height: 300
}
)

reactJS and dynamic render and variable population

I want to dynamically generate output in my render() but I have run into a very strange situation. I have a process whereby I retrieve some data from a database using a fetch(). Once I get the data back, I determine the number of data records and then execute a for loop to populate an array with the returned data. I have a console.log() before the for loop to display the contents of my data receiving array and another console.log() as I populate the receiving array. For some reason, as I populate a specific occurrence of the array, all occurrences of the array appear to be changing. This is the entire code that I have:
import React from 'react';
import '../styles/app.css';
class testpage extends React.Component {
constructor(props) {
super(props);
this.state = {
productArray: []
};
}
componentWillMount() {
var tempData = '';
var numberOfRecords = 0;
let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
var return_code = myJson[0].return_code;
if (return_code == "Default Return code"){
tempData = '';
numberOfRecords = -2;
} else {
tempData = JSON.parse(myJson[0].return_string)
numberOfRecords = tempData.barcode.length;
var counter = 0;
var productArray = new Array;
var product = {
barcode: '',
name: '',
description: '',
image: '',
price: ''
}
for (counter=0;counter<numberOfRecords;counter++) {
product.barcode = tempData.barcode[counter];
product.name = tempData.name[counter];
productArray[counter] = product;
}
}
}
});
}
render() {
<div>
<div>
{this.state.productArray[0].barcode}
</div>
</div>
}
}
export default testpage;
Here is an image of what I see in the console.log() when the loop counter = 0:
Notice the barcode value of 5000159459228? This value gets pushed into productArray[0].barcode. And this is what I expected.
Here is an image of what I see in the console.log() when the loop counter = 1:
Here, the barcode of the record read is 5000159459230. This value should go into productArray1.barcode, which the image shows it does. However, the image also shows that the value of productArray[0].barcode has changed from 5000159459228 (the value of the first record) to 5000159459230 (the value of the second record).
Here is an image from the 3rd time through the loop:
Again, a new record with barcode = 5000159459231. It appears that this value gets pushed into productArray2.barcode but productArray[0].barcode and productArray1.barcode have now been changed.
How is that possible?
Eventually, the goal is to dynamically render the data that is retrieved.
I thank you in advance for any assistance.
Console output can generally be relied on for primitives but not for objects. Console implementation is specific to the environment where a script is executed (browser).
It is object reference that is being outputted, not object snapshot. When an object is changed, its representation in console can change as well.
Notice that in Chrome console the array was initially logged as [] empty array, while next log entry is [...] non-empty array.
To output object snapshot that doesn't change retroactively, use console.log(JSON.stringify(array)) or other serialized primitive value.
I would recommend to use chrome devtools for the debugging and add break point where array is populating.
Also you can add debugger in any where in code It will prompt devtools then.
I second #estus about changes reflect upon object updation where ever you have used Console.
After the chat and learned how the data shape is, with the help of Code Review, this is how we can do this:
const json = [{
return_string: {
"barcode": ["5000159459228", "5000159459229", "5000159459230"],
"scan_date": ["20180825173416", "20180825173416", "20180825173416"],
"name": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)"],
"description": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)"],
"image": ["http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg"],
"price": ["1", "2", "3"]
}
}];
const fakeRequest = () => new Promise( resolve =>
setTimeout( () => resolve( json ) )
);
class App extends React.Component {
state = {
data: "",
}
componentDidMount() {
fakeRequest()
.then( res => {
this.setState({
data: res[0].return_string,
})
})
}
renderData = () => {
const { data } = this.state;
const values = Object.values(data);
const keys = Object.keys(data);
const transposed = values[0].map((col, i) =>
values.map(row => row[i]));
const items = transposed.map(itemArr =>
keys.reduce((acc, key, i) => (
{ ...acc, [key]: itemArr[i] }), {})
);
return items.map( item => (
<div style={{ border: "1px solid black", marginBottom: "3px" }}>
<p>Barcode: {item.barcode}</p>
<p>Scan date: {item.scan_date}</p>
<p>Name: {item.name}</p>
<p>Description: {item.description}</p>
<p>Image: {item.image}</p>
<p>Price: {item.price}</p>
</div>
) )
}
render() {
return <div>{this.state.data && this.renderData()}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The hard part is converting the data into an array of objects properly. Since, this shape of data is a little bit strange to me :) An object containing properties of arrays. And those array elements are matched by order. Phew :)

Resources