Is XLConnect in R is vectorized for writeNamedRegion()? What is the syntax for it? - vectorization

I want to write to NamedRegions in multiple sheets in an excel workbook using XLConnect from R using vectorization, if possible.
I have a list:
trya
$grp1
var1 var2 var3
1455100 459 21 459
$grp2
var1 var2 var3
936710 463 20 463
I have a workbook template (wb1) containing a worksheet (Sheet1) that contains namedRegions (each a single cell). The namedRegions in Sheet1 are named : "varn1","varn2","varn3"
I can clone Sheet1 to create separate sheets to contain the data for each grp in trya.
cloneSheet(wb1, sheet="Sheet1", name = names(trya))
But..how do I write to the NamedRegions in these sheets? I am happy to use 3 separate passes, one for each NamedRegion to write to the multiple worksheets.
Here is my start at the code for NamedRegion "varn1: using code
example is writeNamedRegion(object,data,name of region,header,rownames)
writeNamedRegion(wb1, trya??? ,name = "varn1",header = FALSE)
I don't know
1. how to generically pick off the elements of trya like: trya[[1]]$var1?
2. how to loop over the cloned sheets, named.... names(trya)?
Any help will be appreciated.
Jan

Many of XLConnect's functions/methods are vectorized. This also holds for writeNamedRegion. The following example illustrates how to write a list of data.frame's to appropriate named regions using a single writeNamedRegion call:
require(XLConnect)
wb = loadWorkbook("test.xlsx", create = TRUE)
mySheets = paste0("MySheet", 1:3)
myNames = paste0("MyName", 1:3)
myData = list(mtcars, CO2, USArrests)
createSheet(wb, name = mySheets)
createName(wb, name = myNames, formula = paste(mySheets, "$A$1", sep = "!"))
writeNamedRegion(wb, data = myData, name = myNames)
saveWorkbook(wb)

Related

Can I pick a cell from every Google Sheet in a Folder?

I want to be able to pick say C3 from a list of Google spreadsheets in a folder.
I have a bunch of structurally identical sheets, but I'd like to be able to provide a sum of the values in C3 across say a hundred sheets in a directory.
Ultimately, would be great to highlight the largest or smallest value of C3 in a directory.
This could be useful in many places where you want to be able to aggregate, aggregate data.
SUGGESTION
If you have hundreds of Google spreadsheet files in a Google Drive folder, I agree with #player0 that it is best to use a script. With the Apps Script, you can:
Automate the process in iterating through Spreadsheet files in your Drive folder.
Filter only the Google Spreadsheet type (e.g you have a bunch of
different file types inside).
Get the range data & process them the way you want.
See this sample below that was derived from existing resources:
Script:
function readSheetsInAFolder() {
//FOLDER_ID is your drive folder ID
var query = '"FOLDER_ID" in parents and trashed = false and ' +
'mimeType = "application/vnd.google-apps.spreadsheet"';
var range = "C3"; //The range to look for on every Spreadsheet files in the Drive folder
var files, pageToken;
var finalRes = [];
do {
files = Drive.Files.list({
q: query,
maxResults: 100,
pageToken: pageToken
});
files.items.forEach(sheet => {
finalRes.push(viewRangeValue(range, sheet.id));
})
pageToken = files.nextPageToken;
} while (pageToken);
const arrSum = array =>
array.reduce(
(sum, num) => sum + (Array.isArray(num) ? arrSum(num) : num * 1),
0
);
var max = Math.max.apply(null, finalRes.map(function(row){ return Math.max.apply(Math, row) })); //Gets the largest number
var min = Math.min.apply(null, finalRes.map(function(row){ return Math.min.apply(Math, row); })); //Gets the smallest number
var sum = arrSum(finalRes) // Gets the sum
console.log('RANGE VALUES: %s \nRANGE: %s \nTOTAL SHEET(s) FOUND: %s \n________________\nSUM OF VALUES: %s \nLargest Value: %s \nSmallest Value: %s',finalRes,range, files.items.length,sum,max,min)
}
function viewRangeValue(range, sheetID) {
var sid = sheetID;
var rn = range;
var parms = { valueRenderOption: 'UNFORMATTED_VALUE', dateTimeRenderOption: 'SERIAL_NUMBER' };
var res = Sheets.Spreadsheets.Values.get(sid, rn, parms);
return res.values.map(num => {return parseInt(num)});
}
Demonstration:
Sample Test Drive Folder (w/ 3 test Spreadsheet files):
Every C3 cell on each of these 3 files contain either 0,10 or 6 value.
On the Apps Script Editor, I've added the Drive & Sheets API on the services:
Result
After running the script:
Resources:
Advanced Drive Service
Drive API Files: list
Sheets API spreadsheets.values.get
Max Value of an array

Importing API data via importJSON

Having a bit of trouble using importJSON for the first time in Google Sheets. My data is importing as truncated and I can't find any way to really filter things the way I'd like.
API source: https://prices.runescape.wiki/api/v1/osrs/1h
I'm using the following command: =IMPORTJSON(B1;B2)
where B1 is the source link, and B2 references any filters I've applied. So far I have no filters.
My result is a truncated list that displays as such:
data/2/avgHighPrice 166
data/2/highPriceVolume 798801
data/2/avgLowPrice 162
data/2/lowPriceVolume 561908
data/6/avgHighPrice 182132
data/6/highPriceVolume 7
data/6/avgLowPrice 180261
data/6/lowPriceVolume 37
data/8/avgHighPrice 195209
data/8/highPriceVolume 4
data/8/avgLowPrice 192880
data/8/lowPriceVolume 40
In the examples I've seen and worked with (primarily the example provided by the Addon), it will naturally pivot into a table. I can't even achieve that, which would be workable although I'm really only looking to ping the markers avgHighPrice and avgLowPrice.
EDIT:
I'm looking for results along the lines of this:
2
6
8
/avgLowPrice
162
180261
192880
/avgHighPrice
166
182132
195209
EDIT2:
So I have one more thing I was hoping to figure out. Using your script, I created another script to pull the names and item IDs
function naming(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','id','name'])
for (let p in eval('data.data')) {
try{result.push([p,data.item(p).ID,data.item(p).Name])}catch(e){}
}
return result
}
Object.prototype.item=function(i){return this[i]};
I'm wondering if it is possible to correlate the item Name with the Item ID from the initial pricing script. To start, the 1st script only list items that are tradeable, while the 2nd list ALL item IDs in the game. I'd essentially like to correlate the 1st and 2nd script to show as such
ID
Name
avgHighPrice
avgLowPrice
2
Cannonball
180261
192880
6
Cannon Base
182132
195209
Try this script (without any addon)
function prices(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/1h'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','avgHighPrice','avgLowPrice'])
for (let p in eval('data.data')) {
try{result.push([p,data.data.item(p).avgHighPrice,data.data.item(p).avgLowPrice])}catch(e){}
}
return result
}
Object.prototype.item=function(i){return this[i]};
You can retrieve informations for naming / from mapping as follows
function naming(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(["id","name","examine","members","lowalch","limit","value","highalch"])
json=eval('data')
json.forEach(function(elem){
result.push([elem.id.toString(),elem.name,elem.examine,elem.members,elem.lowalch,elem.limit,elem.value,elem.highalch])
})
return result
}
https://docs.google.com/spreadsheets/d/1HddcbLchYqwnsxKFT2tI4GFytL-LINA-3o9J3fvEPpE/copy
Integrated function
=pricesV2()
https://docs.google.com/spreadsheets/d/1HddcbLchYqwnsxKFT2tI4GFytL-LINA-3o9J3fvEPpE/copy
function pricesV2(){
var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
let myItems = new Map()
json=eval('data')
json.forEach(function(elem){myItems.set(elem.id.toString(),elem.name)})
var url='https://prices.runescape.wiki/api/v1/osrs/1h'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','name','avgHighPrice','avgLowPrice'])
for (let p in eval('data.data')) {
try{result.push([p,myItems.get(p),data.data.item(p).avgHighPrice,data.data.item(p).avgLowPrice])}catch(e){}
}
return result
}
Object.prototype.item=function(i){return this[i]};

How can I use Deedle to get every row from some key and below?

I want to return every value up to and including some key.
Whilst I could generate every such key and chuck them all into the Get, I suspect this will inefficiently search for the value of every key.
Inspired by this answer, I have come up with the following
let getAllUpTo key (frame:Frame<'key,'col>) : Frame<'key, 'col> =
let endRng = frame.RowIndex.Locate key
let startRng = frame.RowIndex.KeyRange |> fst |> frame.RowIndex.Locate
let fixedRange = RangeRestriction.Fixed (startRng, endRng)
frame.GetAddressRange fixedRange
Is there a built in method for doing this efficiently?
If you want to access a sub-range of a data frame with a specified starting/ending key, you can do this using the df.Rows.[ ... ] indexer. Say we have some data indexed by (sorted) dates:
let s1 = series [
let rnd = Random()
for d in 0 .. 365 ->
DateTime(2020, 1, 1).AddDays(float d) => rnd.Next() ]
let df = frame [ "S1" => s1 ]
To get a part of the data frame starting/ending on a specific date, you can use:
// Get all rows from 1 June (inclusive)
df.Rows.[DateTime(2020, 6, 1) ..]
// Get all rows until 1 June (inclusive)
df.Rows.[.. DateTime(2020, 6, 1)]
The API you are using is essentially what this does under the cover - but you are using a very low-level operations that you do not typically need to use in user code.

Limiting query for one result where there is 2 values are matching

I am trying to query 2 long columns for agents' name, the issue is the names are repeated on 2 tables, one for the total sum of productivity and the other is for total sum of utilization.
The thing is when I query the columns it returns back the numbers for Productivity and Utilization all together.
How can I make the query to search only for Productivity alone and for Utilization alone?
Link is here: https://docs.google.com/spreadsheets/d/12Sydw6ejFobySHUj5JoYkAPbhr0mKoInCWxtHY1W4lk/edit#gid=0
Apps Script would be a better solution in this case. The code below works as follows:
Gets the names from Column D and Column A.
For each name of Column D, it will compare it with each name of Column A (that's the 2 for loops)
If the names coincide (first if), it will check the background color (second if) of the Column A name to accumulate Total Prod and Total Util.
Once it reaches the end of the Column A, writes the values in Total Prod and Total Util (Columns E and F) for each name in D.
function onOpen() { //Will run every time you open the sheet
//Gets the active Spreadsheet and sheet
let sprsheet = SpreadsheetApp.getActiveSpreadsheet();
let sheet = sprsheet.getActiveSheet();
var lastRow = sheet.getLastRow();
var getNames = sheet.getRange(3, 1, lastRow).getValues(); //Names from row 2, col 1, until the last row
var totalNames = sheet.getRange("D4:D5").getValues(); //Change the range for more names
let prodColor = '#f2f4f7'; //hexadecimal codes of the background colors of names in A
let utilColor = '#cfe2f3'; //
for (var i = 0; i < totalNames.length; i++) {
var totalProd = 0, totalUtil = 0; //Starts at 0 for each name in D
for (var j = 0; j < getNames.length; j++) {
if (totalNames[i][0] == getNames[j][0]) {
if (sheet.getRange(j + 3, 1).getBackgroundObject().asRgbColor().asHexString() == prodColor) { //if colors coincide
totalProd += sheet.getRange(j + 3, 2).getValue();
} else if (sheet.getRange(j + 3, 1).getBackgroundObject().asRgbColor().asHexString() == utilColor) {
totalUtil += sheet.getRange(j + 3, 2).getValue();
}
}
}
sheet.getRange(i+4, 5, 1 ,2).setValues([[totalProd, totalUtil]]);
}
}
Note: You will have to run the code manually and accept permissions the first time you run it. After that it will run automatically each time you open the Sheet. It might take a few seconds for the code to run and to reflect changes on the Sheet.
To better understand loops and 2D arrays, I recommend you to take a look at this.
References:
Range Class
Get Values
Get BackgroundObject
Set Values
You can learn more about Apps Script and Sheets by following the Quickstart.

VALUE! Ignore text in excel cell calculation

I often run into VALUE! errors in my calculations because they contain numbers and text. How can I leave the text in the cell and proceed with the calculation?
For example:
cell A1 contents look like this: 101.1 J
cell A2 contents look like this: 500 U
cell A3 contents look like this: 0.2
If I want to add A1+A2+A3 into cell A4, how can I ignore the J and U to calculate 101.1+500+0.2 to get 602.3 in cell A4?
Thanks!
You need extract the values from the strings - and this can only be done if you have some kind of information about the format to the numbers. In your example, you could place the following formula in B1:B3 and then add a =SUM(B1:B3):
=IF(ISNUMBER(A1),A1,VALUE(LEFT(A1,SEARCH(" ",A1)-1)))
The above formula will extract the number and convert it to a value - unless it was already a number.
Using Custom Function
Place below code in Standard Module
Function add_num(cell1, ParamArray Arr() As Variant)
Dim temp As Double
For i = LBound(Arr) To UBound(Arr)
temp = temp + GetNumber(Arr(i))
Next
add_num = GetNumber(cell1.Value) + temp
End Function
Function GetNumber(ByVal str As String) As Double
Dim objRegEx As Object
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.IgnoreCase = True
objRegEx.Global = True
objRegEx.Pattern = "\d{1,2}([\.,][\d{1,2}])?"
Set allMatches = objRegEx.Execute(str)
For i = 0 To allMatches.Count - 1
result = result & allMatches.Item(i)
Next
GetNumber = result
End Function
add_num function can be called from excel interface using =addnum(<cells>). It accepts multiple cells.

Resources