I have a function yahoofinance which queries Yahoo!Finance for stock data based on a stock ticker. Before I can call the function, I need to do some modifications to the input.
Tickers in my sheet are of the form [venue].[company], for example XNAS.GOOG (Google at Nasdaq), XAMS.INGA (ING Bank at Amsterdam) and XSWX.CRSP (Crispr at Swiss Exchange).
The codes that I use for the venues are international, but most API's don't use these same codes. For instance, the Amsterdam stock exchange officially is XAMS, but AMS on Google API and AS on Yahoo. So I have a sheet called Exchanges with all these venues listed. When calling my custom yahoofinance function, it takes the standard 'XMAS.INGA' as input, then cuts off the venue (XAMS) and looks up the Yahoo name (AS), then passes INGA.AS to the Yahoo API:
=yahoofinance(index(split(A2, "."), 0, 2) & "." & vlookup(index(split(A2, "."), 0, 1), Exchanges!E:J, 6, false))
function yahoofinance(ticker) {
// send ticker to Yahoo API
}
The logic, which is now in cell B2 in the sheet, is getting complicated to follow. I would like to move it, if possible, into the function and pass only the ticker from A2:
=yahoofinance(A2)
function yahoofinance(ticker) {
const venue = ticker.split();
// etc
// end up with INGA.AS, and pass to Yahoo API
}
My question is: is it possible to move the logic that is now in my sheet, into the function? For example, if and how can I perform the same vlookup from within yahoofinance? Is it even possible?
I am planning to really extend the functionality of yahoofinance, perhaps even rework it to a generic finance function which also takes as argument to which API you want to pass a ticker: =yahoofinance(ticker, api), so any help to get me started with a solid basis here is greatly appreciated.
You can try this custom formula to get the modified value:
Script:
function yahoofinance(ticker) {
var [venue, company] = ticker.split("\.");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Exchanges');
var lastRow = sheet.getLastRow();
// get Exchanges!E:J values
var mapping = sheet.getRange(1, 5, lastRow, 6).getValues();
// lookup equivalent, filter then get 6th column and append to company
var modifiedTicker = company + '.' + mapping.filter(row => row[0] == venue).flat()[5];
// you now have the modified ticker. Use it on your function.
// ...
return modifiedTicker;
}
B7 formula:
=index(split(A2, "."), 0, 2) & "." & vlookup(index(split(A2, "."), 0, 1), Exchanges!E:J, 6, false)
Output:
Related
I am self taught with Apps Script, so generally approach one problem at a time, as it comes up. Arrays are confusing!
I am using an API to get the number of social followers for Facebook, Twitter and Instagram accounts. Here is the code so far. Note I have removed the API call specifics for privacy, I have also used fake profile ID's in the above, for example 1111 = Facebook, 2222 = Twitter, etc...
var response = UrlFetchApp.fetch(endpointUrl, params);
var jsonss = JSON.parse(response.getContentText());
var dataset = jsonss.data;
Logger.log(dataset);
[{dimensions={customer_profile_id=1111, reporting_period.by(day)=2021-10-31}, metrics={lifetime_snapshot.followers_count=407919.0}}, {dimensions={reporting_period.by(day)=2021-10-31, customer_profile_id=2222}, metrics={lifetime_snapshot.followers_count=1037310.0}}, {dimensions={reporting_period.by(day)=2021-10-31, customer_profile_id=3333}, metrics={lifetime_snapshot.followers_count=806522.0}}]
then map the array -
var followers = dataset.map(function(ex){return [ex.dimensions,ex.metrics]});
Logger.log(followers);
[[{reporting_period.by(day)=2021-10-31, customer_profile_id=1111}, {lifetime_snapshot.followers_count=407919.0}], [{reporting_period.by(day)=2021-10-31, customer_profile_id=2222}, {lifetime_snapshot.followers_count=1037310.0}], [{customer_profile_id=3333, reporting_period.by(day)=2021-10-31}, {lifetime_snapshot.followers_count=806522.0}]]
Now I get stuck, I am not sure how to get 'followers_count' when 'profile_id=1111', can someone please help? I have tried using another map function ( var followers = dataset.map(function(ex){return [ex.dimensions.map(function(ex2){return [ex2.followers_count]}]}); ) however this doesn't work...
Any suggestions to push me in the right direction is very much appreciated!
If Logger.log(followers); is [[{reporting_period.by(day)=2021-10-31, customer_profile_id=1111}, {lifetime_snapshot.followers_count=407919.0}], [{reporting_period.by(day)=2021-10-31, customer_profile_id=2222}, {lifetime_snapshot.followers_count=1037310.0}], [{customer_profile_id=3333, reporting_period.by(day)=2021-10-31}, {lifetime_snapshot.followers_count=806522.0}]] and you want to retrieve the value of lifetime_snapshot.followers_count: 407919 by using customer_profile_id: 1111, how about the following sample script?
Sample script:
In this sample script, your values of followers is used.
const profile_id = 1111; // Please set customer_profile_id you want to use.
const res = followers.reduce((ar, [a, b]) => {
if (a["customer_profile_id"] == profile_id) {
ar.push(b["lifetime_snapshot.followers_count"]);
}
return ar;
}, []);
if (res.length == 0) return;
const followers_count = res[0];
console.log(followers_count);
When this script is used for your values of followers, I thought that 407919 is retrieved.
If the same IDs are existing, you can retrieve them using console.log(res).
Reference:
reduce()
I have two sheets. Test Data has 3-4k entries of many columns of data and Order Changes has no data at all. I would like to search two specific columns on Test Data, a column of names and a column of yes or no. If column two of Test Data contains a 'yes' in the cell then the name of that person would be placed into a cell on order changes.
This is what I have so far:
function isThreshold(){
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Test Data");
var cdata = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Order Changes");
var lc = data.getLastColumn();
var lookUp = data.getRange(1,6,3,2).getValues();
lookUp.forEach(var info in lookUp){
}
Logger.log(lookUp);
}
I probably shouldn't loop through that many entries but I don't know of any other way. Should I combine the forEach loop with an if loop to get the desired result or use some other method?
I believe your goal as follows.
You want to retrieve the values from the cells "F1:G" of sheet "Test Data".
You want to search yes from the column "G" and when the column "G" is yes, you want to put the value of the column "F" to the sheet "Order Changes".
Modification points:
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In this case, you can retrieve the values from the range of "F1:G" + data.getLastRow() of "Test Data", and create the array for putting to the sheet "Order Changes", and put it.
When above points are reflected to your script, it becomes as follows.
Modified script:
function isThreshold(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("Test Data");
var cdata = ss.getSheetByName("Order Changes");
var valuesOfTestData = data.getRange("F1:G" + data.getLastRow()).getValues();
var valuesForOrderChanges = valuesOfTestData.reduce((ar, [f, g]) => {
if (g.toLowerCase() == "yes") ar.push([f]);
return ar;
}, []);
if (valuesForOrderChanges.length > 0) {
cdata.getRange(1, 1, valuesForOrderChanges.length, valuesForOrderChanges[0].length).setValues(valuesForOrderChanges);
// or cdata.getRange(cdata.getLastRow() + 1, 1, valuesForOrderChanges.length, valuesForOrderChanges[0].length).setValues(valuesForOrderChanges);
}
}
In this modified script, from your question, it supposes that the columns "F" and "G" are the value of name and yes or no.
References:
getRange(a1Notation) of Class Sheet
reduce()
I'm trying to get all organic entrances for a single URI. I filtered for ga:pagepath==uri and tried to use the segment ga:organicSearches. However the segment doesn't seem to work! I get the following error: "Invalid value 'ga:organicSearches' for segment parameter" Any ideas on how to fix this?
Here is my funtion:
function getEntrancesForUri(uri) {
var endDate = '2016-01-26';
var startDate = '2015-12-28';
var profileId = xxxxxxxx;
var tableId = 'ga:' + profileId;
var optArgs = {
'filters': 'ga:pagePath=='+uri,
'segment': 'ga:organicSearches'
};
var result = Analytics.Data.Ga.get(
tableId,
startDate,
endDate,
'ga:entrances',
optArgs
);
if (result) {
return result;
} else {
return 0;
}
}
That is not how you construct a segment. Also ga:organicSearches is a metric, and you probably want to segment by a dimension.
You can use a dynamic segment as described here which would probably look like this:
sessions::condition::ga:medium==organic
This segments out sessions that have arrived via an organic search.
Alternatively you can create your segment in the GA interface and find the segment id via the Query Explorer, and use that in your query. Testing your queries in the Query Explorer is a good idea in any case, since you get instant feedback and sometimes even a useful error message.
I need to synchronize the contents of 2 spreadsheets that reference each other, keeping their rows in sync if a new row is added in one of the sheets.
I've got 2 spreadsheets in Google Sheets (although if there is a cross spreadsheet solution, both Excel and GS that would be great):
Spreadsheet1 has data in A:F and party1 (a set of users) writes their data in it.
Spreadsheet2 is and import range of A:F from spreadsheet1 and then has further details written in G:M, the data is written in by party2.
The way it works is party1 writes in their data in rows A1-F10 then party2 writes their additional data in spreadsheet2 based on what party1 has written in.
For example if Spreadsheet1 A1:F10 was a name, price, est delivery time, qty etc. of an item, Spreadsheet2 G1:M10 might be a bunch of data on order date, delivered (yes / no) etc.
The issue I'm currently having is that when the spreadsheets are setup they read across fine i.e. 1-10 in spreadsheet1 lines up with 1-10 in spreadsheet2, but after a while some new rows get added into spreadsheet1 between the old rows 2-5. This throws out the order in spreadsheet2 (now row 4 in spreadsheet1 doesn't line up with the row 4 in spreadsheet2 and the data becomes out of line). Is there away around this so that even if someone adds additional rows in the middle of existing rows both spreadsheets will update?
This is a classic problem in database design; how to associate information in two tables. The usual solution is to use key data; one or more columns that exist in both tables and provide a unique identifier, or key, to associate rows.
We can adapt that idea to your situation, with a script that will adjust the location of rows in Spreadsheet 2 to synchronize with Spreadsheet 1. To do that, we need to identify a key - say the Name column - which must exist in both spreadsheets.
This entails a small change in spreadsheet 2, where a Name column will now appear in column G, following the imported range in columns A-F.
A B C D E F G H I J
| Name | Price | est delivery time | qty | etc. of | an item | Name | order date | delivered | blah blah |
< - - - - - - - - - - - - Imported - - - - - - - - - - - > *KEY* < - - - - - - sheet 2 - - - - - >
Demo
Here's how that would look in action! This example is using two sheets in the same spreadsheet, just for convenience. In the demo, a new "Item" row is added in the middle of sheet 1, which automatically appears on sheet 2 thanks to the =IMPORTRANGE() function. The synchronizing function is running on a 1-minute timed Trigger, and you'll see it move things around about 20 seconds in.
You can grab a copy of the spreadsheet + embedded script here.
Code
/**
* Call syncTables() with the name of a key column.
*/
function doSyncTables() {
syncTables( "Name" );
}
/*
* Sync "Orders" spreadsheet with imported rows from "Items" spreadsheet.
*
* From: http://stackoverflow.com/a/33172975/1677912
*
* #param {String} keyName Column header used as key colum, appears
* at start of "Orders" data, following
* "Items" data.
*/
function syncTables( keyName ) {
var sheet2 = SpreadsheetApp.openById( sheetId2 ).getSheetByName('Orders');
// Get data
var lastCol = sheet2.getLastColumn();
var lastRow = sheet2.getLastRow(); // Includes all rows, even blank, because of =importRange()
var headers = sheet2.getRange(1, 1, 1, lastCol).getValues()[0];
var keyCol = headers.lastIndexOf( keyName ) + 1;
var itemKeys = sheet2.getSheetValues(1, 1, lastRow, 1).map(function(row) {return row[0]});
var itemData = sheet2.getSheetValues(1, 1, lastRow, keyCol-1);
var orderData = sheet2.getSheetValues(1, keyCol, lastRow, lastCol-keyCol+1);
var ordersByKey = []; // To keep track of orders by key
// Scan keys in orderData
for (var row=1; row<orderData.length; row++) {
// break loop if we've run out of data.
var orderKey = orderData[row][0];
if (orderKey === '') break;
ordersByKey[ orderKey ] = orderData.slice(row, row+1)[0];
var orderKey = orderData[row][0];
}
var newOrderData = []; // To store reordered rows
// Reconcile with Items, fill out array of matching orders
for (row = 1; row<itemData.length; row++) {
// break loop if we've run out of data.
var itemKey = itemData[row][0];
if (itemKey === '') break;
// With each item row, match existing order data, or add new
if (ordersByKey.hasOwnProperty(itemKey)) {
// There is a matching order row for this item
newOrderData.push(ordersByKey[itemKey]);
}
else {
// This is a new item, create a new order row with same key
var newRow = [itemKey];
// Pad out all columns for the new row
for (var col=1; col<orderData[0].length; col++) newRow.push('');
newOrderData.push(newRow);
}
}
// Update spreadsheet with reorganized order data
sheet2.getRange(2, keyCol, newOrderData.length, newOrderData[0].length).setValues(newOrderData);
}
the current answer by mogsdad is great as always. i just wanted to point out a less complex alternative:
if you can live with preventing spreadsheet1 from allowing insertions or deletion of rows, you will avoid the issue. instead of removing rows you could use a column to mark "deleted" for example (and use filters to remove from view).
to prevent row insertions and deletions in spreadsheet1, simply select an entire unused column to the right, and create a protected range so none of the editors have permission. that prevents modifying at the row level up to the last existing row (but new rows can still be inserted below the range)
it also doesnt prevent users from swapping two row's data. but its still good to know about this simpler alternative.
I am trying to visualize a flickr dataset using protovis. I do understand the visualization part, but i have a question about accessing the data however. I was provided an example visualization and it accesses the data as folllowing:
var data = pv.range(250).map(function(row) {
return {
views: parseInt(Data.data(row, 2)), //refers to the 4 row and 2nd collumn in CSV
users: Data.data(row, 6),
date: Data.data(row, 8))), //more collumns excist but for now we only use these
};
});
As i understand a part of the data set is now stored in the variable data, namely views, users and date. Is this variable able to beaccessed like a dictionary?
What i am trying to do is checking whether there are date on which one user occurs more than 2 times. I thought of looping through the var data as follows:
dateUserDict {};
for (d=0; d < data.date.length; d++ ){
for (i=0; i < data.users.length; i++ ){
for (j=0; j < data.users.length; j++){
if (data.users[i] == data.users[j]){
userCounter++ //this should count the number of occurences of a specific user on a specific date
dateUserDict[data.date] = [data.user][userCounter]}
}
}
}
This does not seem to work. I am trying to store the events (the number of times a user occurs on a specific date) in a dictionary. If i get the dictionary as described i can easily visualise the whole thing. But it is this conversion from the first dict (data) to the second (dateUserDict) which bugs me!
Any help or a push is highly appreciated!
Thanks
jorrit
The function you provided will product a Javascript array of objects.
var data = pv.range(250).map(function(row) {
return {
views: parseInt(Data.data(row, 2)), //refers to the 4 row and 2nd collumn in CSV
users: Data.data(row, 6),
date: Data.data(row, 8))), //more collumns excist but for now we only use these
};
});
The result will look something like this:
var data = [ {views:10, users: 9, date: '09/13/1975'}, ... ]
So instead of using data.users.length, use data.length, and instead of data.users[i], you should be using data[i].users, etc.